Skip to content

Latest commit

 

History

History
543 lines (394 loc) · 16.2 KB

blueprints.md

File metadata and controls

543 lines (394 loc) · 16.2 KB

Have you ever wanted to define your own custom boilerplate when you create new files in an Ember app? You can do that with blueprints. Blueprints are snippet generators used to create the entities — components, routes, services, models and more — used in your applications. Blueprints allow us to share common Ember patterns in the community. Developers can define blueprints for use in their applications or addons.

Ember's built-in blueprints are a source of detailed examples to help you learn more about blueprints. The Ember CLI API docs on blueprints provide advanced information for developing blueprints.

To see a list of all available blueprints with short descriptions of what they do, run ember generate --help or ember help generate.

Generating Blueprints

This is an example of how to generate a standard Route blueprint.

 ember generate route foo

installing route
  create app/routes/foo.js
  create app/templates/foo.hbs
updating router
  add route foo
installing route-test
  create tests/unit/routes/foo-test.js

As the example shows, blueprints can create files and change existing project files.

Defining a Custom Blueprint

You can define your own blueprints using ember generate blueprint <name>:

ember generate blueprint foo

installing blueprint
  create blueprints/foo/index.js

If you want to also generate tests with the foo blueprint, you need to create a foo-test blueprint.

ember generate blueprint foo-test

installing blueprint
  create blueprints/foo-test/index.js

Blueprints in your project’s directory take precedence over those packaged with Ember CLI. This makes it easy to override the built-in blueprints by generating one with the same name.

Blueprint Structure

Blueprints follow a simple structure. Let’s use the built-in helper blueprint as an example:

  blueprints/helper
  ├── files
  │   ├── __root__
  │   │   └── helpers
  │   │       └── __name__.js
  └── index.js

The accompanying test is in another blueprint. It has the same name with a -test suffix and will be generated with the helper blueprint.

  blueprints/helper-test
  ├── files
  │   └── tests
  │       └── integration
  │           └── helpers
  │               └── __name__-test.js
  └── index.js

When creating a custom blueprint, only the index.js file is created. The files directory structure and all template files must be manually created.

files

The files directory has file templates to be installed into the target directory.

__name__

The name token is replaced with the dasherized entity name at install time. For example, when the user invokes ember generate controller foo then __name__ becomes foo.

__root__

The __root__ token is replaced with either app or addon depending upon where it is being generated. This token is used to provide support for generating blueprints inside addons.

Template Variables (AKA Locals)

Variables can be inserted into templates with <%= someVariableName %>.

For example, the built-in util blueprint files/app/utils/__name__.js looks like this:

export default function <%= camelizedModuleName %>() {
  return true;
}

<%= camelizedModuleName %> is replaced with the real value at install time.

Invoking

ember generate util count-down

would create

export default function CountDown() {
  return true;
}

Out of the box, Ember CLI provides the following template variables:

  • dasherizedPackageName
  • classifiedPackageName
  • dasherizedModuleName
  • classifiedModuleName
  • camelizedModuleName

packageName is the project name as found in the project’s package.json.

moduleName is the name of the entity being generated.

More custom variables can be created using the locals hook, as documented below.

Index.js

Overriding the blueprint hooks allows for implementing custom installation and uninstall behavior. The blueprint's description and command options are also defined in the index file.

For example, ember help generate foo would show

   Requested ember-cli commands:

ember generate <blueprint> <options...>
  Generates new code from blueprints.
  aliases: g
  --dry-run (Boolean) (Default: false)
    aliases: -d
  --verbose (Boolean) (Default: false)
    aliases: -v
  --pod (Boolean) (Default: false)
    aliases: -p, -pods
  --classic (Boolean) (Default: false)
    aliases: -c
  --dummy (Boolean) (Default: false)
    aliases: -dum, -id
  --in-repo-addon (String) (Default: null)
    aliases: --in-repo <value>, -ir <value>
  --in (String) (Default: null) Runs a blueprint against an in repo addon. A path is expected, relative to the root of the project.

      foo <name>

index.js should export a plain object, which will extend the prototype of the Blueprint class. If needed, the original Blueprint prototype can be accessed through the _super property.

module.exports = {
  description: "Generates a foo",

  availableOptions: [
    {
      name: "type",
      type: String,
      default: ""
    }
  ],

  locals(options) {
    // Return custom template variables here
    return {};
  },

  normalizeEntityName(entityName) {
    // Normalize and validate entity name here
    return entityName;
  },

  fileMapTokens(options) {
    // Return custom tokens to be replaced in your files
    return {
      __token__(options) {
        // Logic to determine value goes here
        return "value";
      }
    };
  },

  filesPath(options) {
    // Override the default files directory
    // Useful for switching between file sets conditionally
    return "my-files";
  },

  files() {
    // Override the list of files provided by the blueprint
    // Useful if you want to exclude certain files conditionally
    return ["my-file.js"];
  },

  beforeInstall(options) {},
  afterInstall(options) {},
  beforeUninstall(options) {},
  afterUninstall(options) {}
};

Blueprint Hooks

As shown above, the following hooks are available to blueprint authors:

  • locals
  • normalizeEntityName
  • fileMapTokens
  • filesPath
  • files
  • install
  • beforeInstall
  • afterInstall
  • beforeUninstall
  • afterUninstall

Use locals to add custom template variables. The method receives one argument: options. Options is an object containing general and entity-specific options.

From the command line:

ember generate controller foo --type=array --dry-run isAdmin:true

The object passed to locals looks like this:

{
  "entity": {
    "name": "foo",
    "options": {
      "isAdmin": "true"
    }
  },
  "dryRun": true,
  "type": "array"
}

This hook must return an object or a Promise which resolves to an object. The resolved object will be merged with the before mentioned default locals.

For example, using the command line above we could use isAdmin:true in our generated controller

import Controller from "@ember/controller";

export default Controller.extend({
  isAdmin: <%= adminStatus %>
});

When the controller is generated, the locals function would generate the adminStatus variable

module.exports = {
  locals(options) {
    return {
     adminStatus: options.entity.options.isAdmin
    };
  }
};

You can debug the code in a custom blueprint index.js using the node debugger. For more information, see Debugging Node code in the Appendix under Developer Tools.

normalizeEntityName

Use the normalizeEntityName hook to add custom normalization and validation of the provided entity name. The default hook does not make any changes to the entity name. It verifies an entity name is present and that it doesn't have a trailing slash.

This hook receives the entity name as its first argument. The string returned by this hook is the new entity name.

fileMapTokens

Use fileMapTokens to add custom fileMap tokens for use in the mapFile method. The hook must return an object in the following pattern:

{
  __token__: function(options){
    // logic to determine value goes here
    return 'value';
  }
}

It will be merged with the default fileMapTokens, and can be used to override any of the default tokens.

Tokens are used in the files directory (see files), and get replaced with values when the mapFile method is called.

filesPath

Override the default files directory. Useful for switching between file sets conditionally based on options passed during generation

files

Override the list of files provided by the blueprint. Useful if you want to exclude certain files conditionally.

beforeInstall & beforeUninstall

Called before any of the template files are processed and receives the same arguments as locals. Typically used for validating command line options.

afterInstall & afterUninstall

The afterInstall and afterUninstall hooks receive the same arguments as locals. Use it to perform any custom work after the files are processed. For example, the built-in route blueprint uses these hooks to add and remove relevant route declarations in app/router.js.

install

The install hook installs the blueprint and is not normally used when developing blueprints. The hook can be used for advanced blueprints, for example if you don't want your blueprint to install any files.

See the Ember CLI source for install hook details.

Pod blueprints

Pod-based applications use a different file structure giving you more control to scale and maintain large applications. To support pods, the blueprint needs a different structure. Blueprints supporting pods are universal and will support both pods and classic applications.

To see which blueprints support the --pod option, you can use the help command. For example, ember help generate component will give you the list of options for the component blueprint, one of them being --pod.

Generate supported blueprints with a pods structure by passing the --pod option.

 ember generate route foo --pod

installing route
  create app/foo/route.js
  create app/foo/template.hbs
updating router
  add route foo
installing route-test
  create tests/unit/foo/route-test.js

If you have podModulePrefix defined in your environment, your generated pod path will be prefixed with it.

// podModulePrefix: app/pods
ember generate route foo --pod

installing
  create app/pods/foo/route.js
  create app/pods/foo/template.hbs
installing
  create tests/unit/pods/foo/route-test.js

Blueprints that don't support pods structure will ignore the --pod option and use the default structure. Blueprints that support the pods structure will also use the default structure when generated without the --pod option.

Generate a blueprint that supports the --pod option without the option:

 ember generate route foo

installing route
  create app/routes/foo.js
  create app/templates/foo.hbs
updating router
  add route foo
installing route-test
  create tests/unit/routes/foo-test.js

If you would like to use the pods structure as the default for your project, you can set usePods in .ember-cli:

{
    "usePods": true
}

With usePods turned on, the following would occur when generating a route in the pods structure:

ember generate route taco

installing
  create app/taco/route.js
  create app/taco/template.hbs
installing
  create tests/unit/taco/route-test.js

To generate or destroy a blueprint in the classic structure while usePods is activated, you can use the --classic flag:

ember generate route taco --classic

installing
  create app/routes/taco.js
  create app/templates/taco.hbs
installing
  create tests/unit/routes/taco-test.js

Defining a Custom Pods Blueprint

Blueprints in pods applications are created in the same way as classic applications. You define your own blueprints using ember generate blueprint <name>:

ember generate blueprint foo

installing blueprint
  create blueprints/foo/index.js

Pods Blueprint Structure

Blueprints that support pods structure look a little different. Let’s take the built-in controller blueprint as an example:

  blueprints/controller
  ├── files
  │   ├── __root__
  │   │   └── __path__
  │   │       └── __name__.js
  └── index.js

  blueprints/controller-test
  ├── files
  │   └── tests
  │       └── unit
  │           └── __path__
  │               └── __test__.js
  └── index.js

As with classic Ember applications, only the index.js file is automatically created. The files directory structure and all template files must be manually created.

files

The files directory contains templates for all the files to be installed into the target directory.

__name__

When the --pod flag is used, invoking ember generate controller foo --pod replaces __name__ with controller.

If the blueprint is generated without the --pod option, __name__ would be replaced with foo.

__path__

When the --pod flag is used, invoking ember generate controller foo --pod replaces __path__ with foo (or /foo if the podModulePrefix is defined).

If the blueprint is generated without the --pod option, __path__ becomes controller.

__root__

The root token is replaced with either app or addon. This token is used to provide support for generating blueprints inside addons.

__test__

The __test__ token is replaced with the dasherized entity name and appended with -test at install time.

Pods Template Variables and Blueprint Hooks

Template variables and hooks for pods blueprints are the same as classic applications.

There is a special case where you need to override the fileMapTokens hook for blueprints that support both pods and classic applications.

For example, an Ember route named foo has this structure in a classic application:

  app
   ├── routes
   │   └── foo.js
   └── templates
       └── foo.hbs

In a pods application, the same route would have this structure

  app
   └── foo
       ├── route.js
       └── template.hbs

The blueprint structure for a route or a similar custom blueprint would be:

  blueprints/route
  ├── files
  │   ├── __root__
  │   │   ├── __path__
  │   │   |   └── __name__.js
  │   │   └── __templatepath__
  │           └── __templatename__.hbs
  └── index.js

The index file for the blueprint would override the fileMapTokens hook to assign __templatepath__ and __templatename__:

module.exports = {
  fileMapTokens(options) {
    return {
      __templatepath__(options) {
        if (options.pod)
        return options.dasherizedModuleName;
        else
          return "templates";
      },
      __templatename__(options) {
        if (options.pod)
          return "template";
        else
          return options.blueprintName;
      }
    };
  },
};

The options object passed to fileMapTokens is:

{
  "blueprintName": "route",
  "dasherizedModuleName": "foo",
  "hasPathToken": false,
  "inAddon": false,
  "inDummy": false,
  "inRepoAddon": null,
  "locals": {},
  "originBlueprintName": "route",
  "pod": false,
  "podPath": null
}

As mentioned above, Ember's built-in blueprints provide detailed examples on how to create custom blueprints.