Использование RequireJS в приложениях AngularJS

в 15:49, , рубрики: angular, AngularJS, javascript, javascript framework, require, require.js, Веб-разработка

При написании больших JavaScript-приложений одна из самых простых вещей, которую можно сделать, это разделить код на несколько файлов. Это улучшает поддерживаемость кода, но увеличивает шансы потерять или ошибиться со вставкой тега script в главный HTML-документ. Отслеживание зависимостей затрудняется с ростом числа файлов проекта. Эта проблема присутствует в больших AngularJS приложениях до сих пор. У нас есть целый ряд инструментов, которые заботятся о загрузке зависимостей в приложении.

В этой статье мы рассмотрим, использование RequireJS с AngularJS для упрощения загрузки зависимостей. Мы также рассмотрим, как использовать Grunt для генерации файлов, содержащих модули RequireJS.

Краткое введение в RequireJS

RequireJS это JavaScript-библиотека, которая помогает в «ленивой загрузке» JavaScript-зависимостей. Модули являются обычными JavaScript-файлами с некоторым количеством «синтаксического сахара» RequireJS. RequireJS реализует Asynchronous Module Definition, специфицированное в CommonJS. RequireJS предлагает простой API для создания модулей и обращения к ним.

Для RequireJS требуется главный файл, содержащий базовые конфигурационные данные, такие как пути к модулям и «прокладкам». Следующий фрагмент показывает каркас файла main.js:

require.config({
  map:{
    // Maps
  },
  paths:{
    // Алиасы и пути модулей
  },
  shim:{
    // Модули и их зависимости
  }
});

Нет нужды задавать все модули приложения в секции paths. Они могут быть загружены с использованием относительных путей. Для объявления модуля мы должны использовать блок define().

define([
  // Зависимости
], function(
  // Объекты зависимостей
){
 
  function myModule() {
    // Может использовать объекты зависимостей, полученные выше
  }
 
  return myModule;
});

Модуль может не иметь каких-либо зависимостей. Обычно в конце модуля возвращается объект, но это не обязательно.

Внедрение зависимостей в AngularJS против управления зависимостями в RequireJS

Один из распространенных вопросов, который я слышу от AngularJS-разработчиков, касается разницы в управлении зависимостями в AngularJS и RequireJS. Здесь важно вспомнить, что назначение обеих библиотек совершенно разное. Встроенная в AngularJS система внедрения зависимостей (dependency injection) работает с объектами, необходимыми в компонентах; в то время как управление зависимостями (dependency management) в RequireJS имеет дело с модулями или JavaScript-файлами.

Когда RequireJS пытается загрузить модуль, он сначала проверяет все зависимости и загружает их. Объекты загруженных модулей кешируются и предоставляются, когда те же модули запрашиваются снова. С другой стороны, AngularJS поддерживает инжектор со списком имет и соответствующих им объектов. Объект добавляется в инжектор, когда компонент создается и будет предоставлен, когда на него сошлются через зарегистрированное имя.

Использование RequireJS и AngularJS вместе

Код, включенный в статью, который можно загрузить здесь, является простым приложением, содержащим 2 страницы. Он имеет следующие внешние зависимости:

  • RequireJS
  • jQuery
  • AngularJS
  • Angular Route
  • Angular Resource
  • Angular UI ngGrid

Эти файлы должны быть загружены непосредственно на страницу в приведенном здесь порядке. Еще мы имеем пять собственных файлов, содержащих код необходимых компонентов AngularJS. Давайте посмотрим как эти файлы определяются.

Определение компонентов AngularJS как модулей RequireJS

Любой компонент AngularJS состоит из:

  • объявления функции
  • внедрения зависимостей
  • регистрация в модуле Angular

Из этих трех задач, мы будем выполнять первые две внутри отдельных модулей (RequireJS), в то время как третья задача будет выполнена в виде отдельного модуля, который отвечает за создание модуля AngularJS.

Во-первых, давайте определим блок конфигурации. Блок конфигурации не зависит ни от каких других блоков и в конце возвращает функцию config. Но, прежде чем загрузить модуль config внутри другого модуля, мы должны загрузить все, что необходимо для блока конфигурации. Следующий код содержится в файле config.js:

define([],function(){
  function config($routeProvider) {
    $routeProvider.when('/home', {
      templateUrl: 'templates/home.html', 
      controller: 'ideasHomeController'
    })
    .when('/details/:id',{
      templateUrl:'templates/ideaDetails.html', controller:'ideaDetailsController'})
    .otherwise({redirectTo: '/home'});
  }
  config.$inject=['$routeProvider'];
 
  return config;
});

Обратите внимание на способ внедрения зависимостей, использованный в этом фрагменте. Я использовал $inject, чтобы получить внедренные зависимости, так как функция config, объявленная выше, является простой JavaScript-функцией. Перед закрытием модуля мы возвращаем функцию config, так что она может быть передана в зависимый модуль для дальнейшего использования.

Мы следуем этому подходу для определения любого другого типа компонентов AngularJS, кроме того мы не имеем никакого специфичного для компонентов кода в этих файлах. Следующий фрагмент показывает определение контроллера:

define([], function() {
  function ideasHomeController($scope, ideasDataSvc) {
    $scope.ideaName = 'Todo List';
    $scope.gridOptions = {
      data: 'ideas',
        columnDefs: [
         {field: 'name', displayName: 'Name'},
         {field: 'technologies', displayName: 'Technologies'},
         {field: 'platform', displayName: 'Platforms'},
         {field: 'status', displayName: 'Status'},
         {field: 'devsNeeded', displayName: 'Vacancies'},
         {field: 'id', displayName: 'View Details', cellTemplate: '<a ng-href="#/details/{{row.getProperty(col.field)}}">View Details</a>'}
        ],
        enableColumnResize: true
        };
    ideasDataSvc.allIdeas().then(function(result){
      $scope.ideas=result;
    });
  }
 
  ideasHomeController.$inject=['$scope','ideasDataSvc'];
 
  return ideasHomeController;
});

Модуль Angular для приложения зависит от каждого из модулей, определенных до этого момента. Этот файл получает объекты от всех других файлов и цепляет их к модулю AngularJS. Этот файл может возвращать или не возвращать что-либо как результат, на него можно ссылаться из любого места с помощью angular.module(). Следующий фрагмент определяет модуль Angular:

define(['app/config',
  'app/ideasDataSvc',
  'app/ideasHomeController',
  'app/ideaDetailsController'],
 
  function(config, ideasDataSvc, ideasHomeController, ideaDetailsController){
    var app = angular.module('ideasApp', ['ngRoute','ngResource','ngGrid']);
    app.config(config);
    app.factory('ideasDataSvc',ideasDataSvc);
    app.controller('ideasHomeController', ideasHomeController);
    app.controller('ideaDetailsController',ideaDetailsController);
});

Это приложение Angular не может быть запущено с использованием директивы ng-app, так как необходимые скрипты загружаются асинхронно. Правильный подход здесь состоит в использовании ручного запуска. Это должно быть сделано в специальном файле, именуемом main.js. Здесь нужно, чтобы сначала был загружен файл с определением модуля Angular. Код для этого файла показан ниже.

require(['app/ideasModule'],
  function() {
    angular.bootstrap(document, ['ideasApp']);
  }
);

Конфигурирование Grunt для объединения модулей RequireJS

При развертывании больших JavaScript-приложений файлы скриптов следует объединять и минифицировать для оптимизации скорости их загрузки. Инструменты, подобные Grunt, могут пригодиться для автоматизации этих задач. Он имеет целый ряд задач, определенных чтобы сделать любой процесс front-end развертывания легче. У него есть задача grunt-contrib-requirejs для объединения файлов модулей RequireJS в правильном порядке и последующей минификации результирующего файла. Подобно любой другой задаче Grunt, она может быть сконфигурирована, чтобы вести себя по-разному для каждой стадии развертывания. Следующая конфигурация может быть использована в нашем демо-приложении:

requirejs: {
  options: {
    paths: {
      'appFiles': './app'
    },
    removeCombined: true,
    out: './app/requirejs/appIdeas-combined.js',
    optimize: 'none',
    name: 'main'
  },
  dev:{
    options:{
      optimize:'none'
    }
  },
  release:{
    options:{
      optimize:'uglify'
    }
  }
}

Эта конфигурация будет создавать несжатый файл, когда Grunt будет запущен с опцией dev, и минифицированный файл в случае запуска Grunt с опцией release.

Заключение

Управление зависимостями становится сложным, когда размер приложения превышает определенной количество файлов. Библиотеки, подобные RequireJS, позволяют легче определять зависимости и не беспокоиться о порядке загрузки файлов. Управление зависимостями становится неотъемлемой частью приложений JavaScript. AngularJS 2.0 будет иметь встроенную поддержку для AMD.

UPDATE: Было бы интересно услышать в комментариях, какими менеджерами зависимостей вы пользуетесь и что считаете лучшим вариантом.

Автор: victorburre

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js