Как да: Модернизиран AngularJS 1.5+ с ES6, Webpack, Mocha, SASS и компоненти

Има много причини, поради които може да искате да продължите да работите с AngularJS 1.x, така че просто ще приема, че имате своите причини.

Angular! == AngularJS „Този ​​сайт и цялото му съдържание се отнасят до AngularJS (версия 1.x), ако търсите най-новия Angular, моля, посетете angular.io“ - angularjs.org

За нови проекти бих препоръчал да използвате React, тъй като тук е моментът в развитието на фронта.

Направих репо за GitHub, с което можете да разклоните / клонирате, за да стартирате свой собствен проект:

jsdoc_output // където се генерират документи
node_modules // където отиват вашите доставчици
.gitignore
mocha-webpack.opts // посочете различна конфигурация на уебпакет за тестване
package.json
README.md
webpack.config.base.js
webpack.config.js // разширява базовата конфигурация
webpack.config.test.js // разширява базовата конфигурация
обществен
| index-bundle.js // пакет, генериран от уебпакет
| index.html
| index.js // webpack
|
\ --- superAwesomeComponent
        componentStylez.sass
        componentTemplate.html
        fancyJsModule.js
        theComponent.js
        theComponent.spec.js
        theComponentController.js

Генерирано с помощта на дърво / a / f на прозорци

Нека проверим index.html

<тяло
  ПГ приложенията = "theWholeApp"
  ng-controller = "IndexController като IndexCtrl"
  NG-прикрит>
  <Супер страхотно-компонент
    някои вход = "93"
    some-output = "IndexCtrl.fancyValue = стойност">
  
  <Супер страхотно-компонент
    някои вход = "2"
    some-output = "IndexCtrl.fancyValue = IndexCtrl.fancyValue + IndexCtrl.addValue">
  
  

    Променлива на контролера над компонентите: {{IndexCtrl.fancyValue}}    <Опашка>   

Все още не са предприети действия

Тук можете да видите, че нашите два бутона са двата супер страхотни компонента. Това са ъглови 1.5 компоненти.

Ъглови 1.5 компонента

Ъгловите 1.5 компоненти са само директиви с по-добри стойности по подразбиране. Те винаги са елементи, има по подразбиране „Контролер като $ ctrl“ и имат изолиращ обхват. Повечето от това, което научих от компоненти, научих тук https://toddmotto.com/exploring-the-angular-1-5-component-method/

Компонентите имат две връзки, някои входни и някои изходни.

Тези компоненти са полезни, защото ни позволяват да капсулираме комбинация от изглед и функционалност на контролера. Нека разгледаме компонентния файл

шаблон за импортиране от „./componentTemplate.html“
импортиране на компонентStylez от './componentStylez.sass'
import {ComponentController} от './theComponentController.js'
const обвързвания = {
  someInput: '<',
  someOutput: '&'
}
експортиране const theComponent = {
  контролер: ComponentController,
  шаблон,
  автомати
}

Забележете как всеки елемент от контролера може да бъде използван повторно. Контролерът може да бъде специфичен за този компонент или може да бъде контролер, който се използва другаде.

Освен това, този файл съдържа препратки към всичко, което трябва да знаете за това как компонента. Компонентът е напълно самостоятелен, няма нужда да се притеснявате как се използва в по-голямото приложение, за да го направите.

Контролерът използва нормални функции на ES6 и няма да се впускам в това как работи, но просто отбележете използваната структура на класа и липсата на обхват $. Резултатът е рамково-агностичен контролер, минус използване на събитието на жизнения цикъл на компонента ($ onInit)

импортиране на fancyFunction от './fancyJsModule.js'
/ **
 * Предоставя манипулатори за theComponent
 * /
клас ComponentController {
  / **
   * Обявява, че входните връзки не са дефинирани
   * @return {undefined} undefined
   * /
  конструктор () {
    console.log ('входни връзки не са дефинирани!', this.someInput)
  }
  / **
   * Извиква someOutput със стойността на someInput, пуснат във fancyFunction
   * @return {undefined} undefined
   * /
  doSuperThings () {
    console.log („прави супер неща“)
    this.someOutput ({стойност: fancyFunction (this.someInput, 3)})
  }
  / **
   * Обявява, че входните връзки са дефинирани
   * @return {undefined} undefined
   * /
  $ onInit () {
    console.log ('входни връзки са дефинирани!', this.someInput)
  }
}
експортиране {ComponentController}

StandardJS форматиране

Очевидната разлика е липсата на запетая. Аз лично вярвам, че това осигурява по-чисто изглеждащ код и проблемите около използването на запетая се решават спретнато чрез използване на линията / формат на StandardJS, което ще ви попречи да срещнете странни проблеми там.

Уебпакет (което може да бъде объркващо)

Забележете как трябва да импортираме само index.bundle.js в index.html. Това е така, защото ние използваме Webpack, който свързва всички наши активи в един файл. Това включва нашите шаблони, javascript, css и всичко, което можете да си представите, че има нужда от там.

Уебпак е фин звяр, а звяр е. Това е достатъчно сложно, че хората го поставят върху своите автобиографии. Премества много сложност от различни части на приложението ви във вашия файл webpack.config.js.

Доказателство за тази сложност може да се намери във факта, че имаме причина за 3 файла webpack.config * .js. Единият предоставя база, вторият е да побере нашата тестова настройка, а третият е за разделяне на кода на парчета от доставчици (което не искаме да правим в нашата тестова настройка да правим странни взаимодействия с CommonsChunkPlugin).

var path = изисквам ('път')
var webpack = изисквам ('webpack')
module.exports = {
  запис: {
    'index': path.join (__ dirname, '/public/index.js')
  }
  изход: {
    име на файл: '[име] -bundle.js',
    path: path.join (__ dirname, '/ public /'),
    devtoolLineToLine: вярно,
    pathinfo: вярно,
    sourceMapFilename: '[име] .js.map',
    publicPath: path.join (__ dirname, '/ src / main / webapp /')
  }
  модул: {
    товарачи: [
      {test: /\.js$/, loader: 'babel-loader', изключете: / node_modules /},
      {test: /\.css$/, loader: 'style-loader! css-loader'},
      {test: /\.sass$/, loaders: ['style-loader', 'css-loader', 'sass-loader']},
      {test: /\.html$/, loader: 'raw-loader'},
      // вградени base64 URL адреси за <= 8k изображения, директни URL адреси за останалите
      {test: /\.(png|jpg)$/, loader: 'url-loader? limit = 8192'},
      // помага за зареждане на css на bootstrap.
      {тест: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url? limit = 10000 & minetype = application / font-woff'},
      {тест: /\.woff2$/,
        loader: 'url? limit = 10000 & minetype = application / font-woff'},
      {тест: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url? limit = 10000 & minetype = application / octet-stream'},
      {тест: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'файл'},
      {тест: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url? limit = 10000 & minetype = image / svg + xml'}
    ]
  }
  приставки: [
    нов уебпакет.HotModuleReplacementPlugin ()
  ],
  devServer: {
    publicPath: '/',
    contentBase: path.join (__ dirname, '/ public'),
    компрес: вярно
  }
  devtool: 'eval'
}

Тук няма да обяснявам всичко, защото за това са документите на webpack (тази връзка е за Webpack 1, въпреки че използваме Webpack 2. Документите Webpack 2 са задълбочени само в своята непълнота, но виждаме миграциите документация).

За да дадете преглед, трябва да посочите:

  • Където започва молбата ви
  • Къде върви пакетът
  • Как ще импортирате магически неща
  • Какви плъгини използвате
  • Вашата настройка за webpack-dev-сървър
  • Как са настроени вашите изходни карти.

Какво? Plugins? Източни карти? Защо имам нужда от друг сървър?

Plugins

Тук просто използваме приставката HotModuleReplacement (HMR). Той позволява на нашия браузър автоматично да се презарежда, когато даден файл е променен. Това магически премахва една стъпка от нормалната итерация на запис, записване, тестване.

Има много други плъгини навън (един, който не съм се захванал да опитам, който се откроява: https://github.com/owen-it/ng-loader)

Ето списък с популярни приставки за Webpack: https://github.com/webpack-contrib/awesome-webpack (защо Webpack прави толкова много неща?)

Източни карти

Източните карти са продукт на ES6 и групиране. Все още не съм измислил как да ги постигна перфектни, има злощастна бързина / качествен компромис, която се случва с изходните карти, тъй като перфектните могат да бъдат доста бавни за създаване. Нашата ES6 конверсия се постига чрез багери.

Ако погледнем назад към theComponent.js, това съдържа по-голямата част от нашия уебпакет

шаблон за импортиране от „./componentTemplate.html“
импортиране на компонентStylez от './componentStylez.sass'
import {ComponentController} от './theComponentController.js'
const обвързвания = {
  someInput: '<',
  someOutput: '&'
}
експортиране const theComponent = {
  контролер: ComponentController,
  шаблон,
  автомати
}

Обърнете внимание как тук импортираме html, SASS и ES6. Това се осъществява чрез нашите товарачи. Кой товарач се използва, се основава на името на файла.

WebPACK-Dev-сървър

Webpack-dev-сървърът е невероятно нещо, независимо от това дали имате истински бекенд или не. Той поддържа HMR и е статичен файлов сървър, който прави вашето развитие бързо. В допълнение, използването на webpack-dev-сървър ще ви принуди да деблокирате вашия фронт и резервен.

Да можеш да правиш разработка на фронтенд, без да се нуждаеш от „истински“ сървър е невероятно поради много причини. Това ще ви принуди да създадете практически макетни данни, да знаете точно каква функционалност принадлежи към бекенда срещу фронта, ще ви даде HMR и ще направи вашия фронтенд талисман на почти всеки сървър, с ясен договор между фронта и резервния.

В тази настройка, webpack-dev-сървърът, заедно с всичко останало, необходимо за разработване на интерфейса, се управлява от една команда dev команда npm run, както е посочено в package.json

{
  "name": "modern-angularjs-starter",
  "версия": "0.0.1",
  "description": "Основен проект",
  "main": "index.js",
  "скриптове": {
    "dev": "едновременно --kill-others \" webpack-dev-server --host 0.0.0.0 \ "\" npm стартиране на документи \ "",
    "docs_gen": "jsdoc -r -d jsdoc_output / public /",
    "docs_watch": "гледайте \" npm стартирайте docs_gen \ "public",
    "docs_serve": "echo Документите се обслужват на порт 8082! && live-server -q --port = 8082 --no-browser jsdoc_output /",
    "docs": "едновременно --kill-others \" npm run docs_serve \ "\" npm run docs_watch \ "",
    "postinstall": "инсталиране на bower",
    "webpack": "webpack",
    "test": "mocha-webpack public / ** / *. spec.js"
  }
  "devDependitions": {/ * скрито за пространство * /}
  "зависимости": {/ * скрито за пространство * /}
}

Забележете използването на едновременно (https://www.npmjs.com/package/concurrently)

Това ни позволява да изпълняваме 2 блокиращи команди паралелно.

Забележете, че има и команди за тестване и документация. Командите за документация генерират страници на JSDoc и след това се хостват на малък сървър, който автоматично опреснява (подобно на HMR) браузъра, когато има промяна. По този начин можете да наблюдавате актуализацията на вашите документи, докато ги пишете, ако записвате често.

Той не е демонстриран в този проект, но определянето на типовете в JSDoc е добър начин за определяне на договори за данни между frontend / backkend. Освен това можете просто да използвате typecript (за това има товарачи).

Тестване на единица: (защото си заслужава усилията)

Тестването с ES6 + AngularJS + Webpack е сложно да се оправи. Всяко от тях причинява усложнения. За тестване на единици приключих с много малки единици, като тествах контролерите си AngularJS като функции в Node. Карма е доста популярна, но според мен тестовете всъщност не са единични тестове. Би било полезно обаче да има и двете.

По този начин имаме mocha-webpack. Това ни позволява да използваме внос в нашите тестове, без да посочваме входна точка за всеки от тях.

Най-трудната част от тестването тук е да се подиграваме с вноса на ES6. Има няколко различни начина да направите това, но единственият, който не изисква модификация на тествания файл, беше инжекционният товарач.

Това е особено полезно за писане на тестове, където понякога е необходимо да се подигравате с нещата във вашия модул под тест преди изпълнението. - инжекционен товарач
/ * eslint-disabled * /
импортиране на chai от „chai“
внос sinon от 'sinon'
const theControllerInjector = изисквам ('инжекционен товарач!. / theComponentController.js')
нека {очаква, трябва, твърдя} = chai
description ('superAwesomeComponent', функция () {
  нека мъниче
  нека theComponentController
  нека контролер
beforeEach (настройка на функциятаComponent () {
    stub = sinon.stub (). връща (1)
    theComponentController = theControllerInjector ({
      // Модулът е наистина прост, така че всъщност не е необходимо да му се подигравате
      // В истинско приложение може да е много по-сложно (т.е. нещо, което прави API разговори)
      './fancyJsModule.js': stub
    }). ComponentController
    контролер = нов theComponentController ()
    controller.someOutput = sinon.stub ()
    controller.someInput = 1
  })
  description ('doSuperThings', function () {
    it ('извиква fancyFunction', функция () {
      controller.doSuperThings ()
      отстояват (stub.calledOnce)
    })
  })
})

За да използваме инжекционния товарач, използваме стария синтаксис на товарача на изискване + webpack, защото няма проверка на името на подсказка, която можем да направим за импортирането (не искаме всички js файлове да бъдат предавани през инжекционния товарач през цялото време) , Връщането на това изискване ни дава функция, която можем да извикаме с обект, затрудняващ различни вноси:

theComponentController = theControllerInjector ({
  './fancyJsModule.js': stub
}). ComponentController

Ето, ние изключваме fancyJsModule от вноса на нашия контролер. Това ни позволява да върнем макетна стойност, подривайки цялата логика, която модулът може да направи, за да можем да изолираме всички проблеми, възникнали в теста.

Ние използваме chai като нашата библиотека с твърдения, sinon за подигравки / шпиониране и mocha за провеждане на тестовете.

Този тест не се опитва да бъде добър пример за това какво да тествате, просто е да покажете как може да се настрои тестване с ES6 + Webpack + Mocha + Angular.

Целта на това е да накара разработчика да се съсредоточи върху писането на обработчици на AngularJS като действителни функции. Съществува силна тенденция тези манипулатори да се изпълняват единствено за странични ефекти и създаването на тези тестове ще подчертае този факт.

Су ...

Тази архитектура осигурява начин за модернизиране на фронтала на AngularJS, без да прави скок на рамката. Едно от най-големите предимства на този подход е, че той абстрахира много от специфичния за AngularJS код.

Един от най-трудните елементи на този подход е да решите за какво да използвате AngularJS модули за какво да използвате ES6 модули. Опитвам се да предпочитам ES6 колкото е възможно повече. Това трябва да улесни портирането на приложение, използвайки тази архитектура, в друга рамка.

AngularJS все още има доста голям живот в него, но няма дебат, че той е минал най-напред. ES6 / 7 обаче все още се увеличава (http://vanilla-js.com).

Да живее AngularJS!

Между другото, вижте предишните ми рентове на JS https://medium.com/@narthur157/let-s-talk-about-javascript-bdb0bdf57fae