Ember 注入环境配置的蓝图?

Ember blueprint that injects environment config?

我对 Ember 比较陌生,想知道是否有一种方法可以创建一个 blueprint/generator,它可以在维护所有现有配置的同时向环境配置中注入新值。是否有一些 Ember 魔法允许现有文件充当蓝图模板?我理想的实现看起来像这样:

ember g platform foo

// config/environment.js
module.exports = function(environment) {
var ENV = {
  // Existing config values here...
  APP: {
    platforms: {
      foo: 'abc123'  // Generator injects the 'foo' platform and a GUID
    }
  };
  // Existing environment-specific settings here...
  return ENV;
};

这是否可以使用 Node 的 fs.readFile()fs.writeFile() 更轻松地完成?如果是这样,我该如何解析 environment.js?

不,据我所知,Ember 中不存在魔法,抱歉。当您生成路线时,会发生与您所谈论的非常相似的事情,但代码相当复杂。 ember generate route new_route函数调用了这个函数

function addRouteToRouter(name, options) {
  var routerPath = path.join(options.root, 'app', 'router.js');
  var source = fs.readFileSync(routerPath, 'utf-8');

  var routes = new EmberRouterGenerator(source);
  var newRoutes = routes.add(name, options);

  fs.writeFileSync(routerPath, newRoutes.code());
}

然后执行解释器级别的代码以添加路由器并将其还原为代码:

module.exports = EmberRouterGenerator;

var recast    = require('recast');
var traverse  = require('es-simpler-traverser');

var Scope     = require('./scope');
var DefineCallExpression = require('./visitors/define-call-expression.js');

var findFunctionExpression = require('./helpers/find-function-expression');
var hasRoute               = require('./helpers/has-route');
var newFunctionExpression  = require('./helpers/new-function-expression');
var resourceNode           = require('./helpers/resource-node');
var routeNode              = require('./helpers/route-node');

function EmberRouterGenerator(source, ast) {
  this.source = source;
  this.ast = ast;
  this.mapNode = null;
  this.scope = new Scope();

  this.visitors = {
    CallExpression: new DefineCallExpression(this.scope, this)
  };

  this._ast();
  this._walk();
}

EmberRouterGenerator.prototype.clone = function() {
  var route = new EmberRouterGenerator(this.source);

  return route;
};

EmberRouterGenerator.prototype._ast  = function() {
  this.ast = this.ast || recast.parse(this.source);
};

EmberRouterGenerator.prototype._walk  = function() {
  var scope = this.scope;
  var visitors = this.visitors;

  traverse(this.ast, {
    exit: function(node) {
      var visitor = visitors[node.type];

      if (visitor && typeof visitor.exit === 'function') {
        visitor.exit(node);
      }
    },

    enter: function(node) {
      var visitor = visitors[node.type];

      if (visitor && typeof visitor.enter === 'function') {
        visitor.enter(node);
      }
    }
  });
};

EmberRouterGenerator.prototype.add = function(routeName, options) {
  if (typeof this.mapNode === 'undefined') {
    throw new Error('Source doesn\'t include Ember.map');
  }

  var route = this.clone();
  var routes  = route.mapNode.arguments[0].body.body;

  route._add.call(
    route,
    routeName.split('/'),
    routes,
    options
  );

  return route;
};



EmberRouterGenerator.prototype._add = function(nameParts, routes, options) {
  options = options || {};
  var parent   =  nameParts[0];
  var name     = parent;
  var children = nameParts.slice(1);
  var route    = hasRoute(parent, routes);

  if (!route) {
    if (options.type === 'resource') {
      route = resourceNode(name, options);
      routes.push(route);
    } else {
      route = routeNode(name, options);
      routes.push(route);
    }
  }

  if (children.length > 0) {
    var routesFunction = findFunctionExpression(route.expression.arguments);

    if (!routesFunction) {
      routesFunction = newFunctionExpression();
      route.expression.arguments.push(routesFunction);
    }

    this._add(children, routesFunction.body.body, options);
  }
};

EmberRouterGenerator.prototype.remove = function(routeName) {
  if (typeof this.mapNode === 'undefined') {
    throw new Error('Source doesn\'t include Ember.map');
  }

  var route = this.clone();
  var routes  = route.mapNode.arguments[0].body.body;

  var newRoutes = route._remove.call(
    route,
    routeName.split('/'),
    routes
  );

  if (newRoutes) {
    route.mapNode.arguments[0].body.body = newRoutes;
  }

  return route;
};



EmberRouterGenerator.prototype._remove = function(nameParts, routes) {
  var parent   =  nameParts[0];
  var name     = parent;
  var children = nameParts.slice(1);
  var route    = hasRoute(parent, routes);
  var newRoutes;

  if (children.length > 0) {
    var routesFunction = route.expression && findFunctionExpression(route.expression.arguments);

    if (routesFunction) {
      newRoutes = this._remove(children, routesFunction.body.body);

      if (newRoutes) {
        routesFunction.body.body = newRoutes;
      }

      return routes;
    }
  } else {
    if (route) {
      routes = routes.filter(function(node) {
        return node !== route;
      });

      return routes;
    } else {
      return false;
    }
  }
};

EmberRouterGenerator.prototype.code = function(options) {
  options = options || { tabWidth: 2, quote: 'single' };

  return recast.print(this.ast, options).code;
};

那么还有一个替代方案,它涉及读取文件,在正确解析文件后将新环境添加到正确的位置,然后将流写回。你想要做的事情的复杂性可能超过了手动执行 IMO 所花费的时间。如果这是你经常做的事情,也许可以考虑在文本文件操作中用另一种更好的语言编写脚本(阅读更多人使用它)