Как написать свой фреймворк js

Обновлено: 05.07.2024

Что мы будем делать: мини-фреймворк для анимации перемещения абсолютно или относительно позиционированных на странице объектов по оси X или Y.

Сначала о главном: это мой первый опыт написания уроков, да и вообще статей, так что сильно не пинайте.

У многих возникнет вопрос, зачем все это надо, если есть уже готовые фреймворки (jQuery, Prototype и т.п.)? Многие скажут излюбленную фразу про велосипед. Но вы их не слушайте, они просто буржуи – любят жить на всем готовеньком. А вот мы с вами – рабочий класс! А если серьезно, то иногда нет смысла подключать большой фреймворк ради одной единственной функции, в нашем случае анимации перемещения объекта. Ну и, опять же, опыт.

Для начала немного про ООП

В JavaScript’е объект можно создать двумя способами. Первый способ создание функции. В JavaScript объектом является любая функция:

Ключевое слово this является ссылкой на объект Obj. This.x и this.y – это его свойства Obj, т.е., говоря просто, это локальные переменные которые действуют только внутри объекта Obj. Так же у объекта есть методы. Метод – это функция внутри объекта. Работает она тоже только внутри него. У каждого объекта в JavaScript есть свойство prototype, при помощи которого можно добавлять новые методы объекту:

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

Неудобство такого подхода в создании объектов заключается в том, что в данном случае объект надо обязательно инициализировать при помощи ключевого слова new. Тут на помощь приходит второй способ создания объекта, а именно объектный литерал:

Внутри объектного литерала свойства и методы записываются парами свойство: значение (имя_метода: функция) и отделяются запятыми. Похоже на ассоциативный массив ключ=>значение.

При таком стиле создания объекта нет необходимости в ключевом слове new и объект можно использовать сразу:

Но тогда остается вопрос, как передать объекту параметры? Для этого можно создать отдельный метод, скажем init (я так и делаю), где параметры будут присвоены соответствующим свойствам объекта. Там же можно вызывать какой-либо метод:

Так же в качестве параметра функции можно передать объект, для этого нужно записать в параметр объектный литерал, который потом можно использовать внутри нашего объекта. Для этого надо немного изменить метод init:

Преимущества такого подхода заключаются в том, что нам становится совершенно неважно в каком порядке стоят передаваемые параметры:

А еще, при таком подходе мы можем добавлять дефолтные значения для некоторых параметров:

Ну вот теперь вы должны немного представлять себе как устроены объекты в JavaScript'е и мы можем приступать собственно к построению самого фреймворка. Следите за темой, продолжение в следующем посте.

Что почитать на тему:

Вопросы, предложения, критика и грубая лесть приветствуется. Прошу все писать в личку. Тему закрою во избежание флуда.

Вы когда-нибудь задавались вопросом, как работают фреймворки?

Когда я впервые открыл для себя AnglurJS после изучения jQuery он показался мне черной магией.

Затем вышел Vue.js. В процессе анализа его работы под капотом я попытался написать свою собственную реализацию двусторонней привязки.


В этой статье я покажу вам как написать современный JavaScript фреймворк с пользовательскими атрибутами HTML-элементов, реактивностью и двойным связыванием.

Как работает реактивность?

Идея, которая стоит за паттерном, крайне проста и заключается в перегрузке методов доступа к объекту.

К примеру, вы не можете получить прямой доступ к своему счету в банке и поменять сумму. Необходимо попросить об этом кого-то с доступом к счету, в данном случае это ваш банк.

В примере выше при запросе поля account из объекта bank геттер перегружен, поэтому всегда возвращается 9,000,000 вместо значения поля. Даже, если поле не существует.


Благодаря перегрузке функции set становится возможным управление поведением объекта. Вы можете изменить значение поля объекта, обновить другое поле или даже не делать ничего.

Пример реактивности

Теперь, когда вы уже знаете, как работает паттерн проектирования Proxy, давайте напишем свой собственный JavaScript фреймворк.

Для простоты наш синтаксис будет близок к AngularJS. Объявление контроллера и привязка элементов шаблона к свойствам контроллера довольно проста.

В начале мы определили контроллер с его полями. Затем использовали этот контроллер в шаблоне. Наконец, использовали атрибут ng-bind для доступа к двусторонней связке со значениями элементов.

Парсинг шаблона и создание экземпляра контроллера

Для того, чтобы свойства связывались, нам необходимо получить место для объявления этих свойств. Таким образом, необходимо определить контроллер и добавить его в нашу структуру.

Во время объявления контроллера наш фреймворк будет искать элементы, которые содержат атрибут ng-controller.

Если будет найден атрибут с указанием одного из объявленных контроллеров, фреймворк создаст новый экземпляр этого контроллера. Экземпляр контроллера несет ответственность только за конкретную часть шаблона.

Вот так выглядит объявление переменной контроллера своими руками.Объект controllers содержит все контроллеры, объявленные в рамках структуры путем вызова addController.


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

Поиск привязок

На этот раз мы получаем экземпляр нашего контроллера и часть шаблона.

Достаточно просто. Фреймворк сохраняет все связи как объект. Для этого он использует отображение на основе хешей (hash map). Эта переменная хранит все поля для связки с конкретным значением и всеми его DOM элементами.


Двусторонняя привязка полей контроллера

После того как наш фреймворк начал легко справляться с простейшими задачами, мы можем перейти к более интересной части, которая называется двусторонней привязкой.

Такая задача включает в себя как связь полей контроллера к DOM элементам, так и наоборот. На этот раз, когда пользователь захочет совершить какое-то действие с элементом, поле контроллера будет изменено соответствующим образом. Затем обновятся все остальные элементы вокруг этого поля.

Поиск изменений в коде с proxy

Как говорилось выше, Vue оборачивает компоненты в proxy для динамического изменения полей. Давайте повторим такой подход и обернем сеттер полей контроллера.

Всякий раз, когда в связующее поле будет добавляться новое значение, прокси будет проверять все связи с этим свойством. Затем они обновятся новым значением.

В нашем примере мы реализовали привязку только к input элементу, т.к. только в value атрибут заносится новое значение.

Реакция на события от элементов

Нам необходимо слушать эти события и обновлять поля, которые связаны с событием. Все остальные элементы, которые привязаны к нашему полю, будут обновлены автоматически.

Соединив все вместе, вы получаете реализацию двусторонней привязки своими руками.

Спасибо за чтение. Надеюсь статья помогла вам понять, как работают JavaScript фреймворки.

И я вас поздравляю! Вы разработали собственными руками такие популярные возможности, как пользовательские атрибуты HTML элементов, реактивность и двустороннюю привязку.

JavaScript

Суть модульного тестирования заключается в проверке небольших изолированных фрагментов кода. Если тест использует внешний ресурс, например сеть или базу данных, он уже не является модульным.

Фреймворки модульного тестирования описывают тесты в человекочитаемом формате, чтобы те, кто не связан с технической сферой, смогли в них разобраться. Однако даже если вы являетесь представителем технической сферы, тесты в формате BDD могут значительно облегчить поиск проблемы.

Например, чтобы протестировать следующую функцию:

Нужно написать тест jasmine spec:

② Функция it(string, function) определяет отдельную спецификацию теста, которая содержит одно или несколько ожиданий теста.

Setup и teardown

В некоторых случаях при тестировании функции необходимо выполнить настройку. Например, создать несколько тестовых объектов. Кроме того, после завершения теста может потребоваться очистка. Например, удаление файлов с жесткого диска.

Эти действия называются setup и teardown (для очистки). В Jasmine есть несколько функций для упрощения этого процесса:

beforeAll вызывается один раз перед запуском всех спецификаций в тестовом наборе.

afterAll вызывается один раз после завершения всех спецификаций в тестовом наборе.

beforeEach вызывается перед каждой спецификацией теста, если функция запущена.

afterEach вызывается после выполнения каждой спецификации теста.

Использование в Node

В проекте Node файлы модульного теста определяются в папке test в одной директории с папкой src :

Тест содержит файлы спецификации, которые являются модульными тестами для файлов в папке src. В package.json test находится в разделе script .

Если в командной строке запущен npm run test , тестовый фреймворк jest запустит все файлы спецификации в папке test и отобразит результат в командной строке.

Переходим к созданию собственного тестового фреймворка, который будет работать на Node.

Начнем с создания проекта Node:

Устанавливаем зависимость chalk, с помощью которой мы будем раскрашивать результаты тестов: npm i chalk .

Создаем папку lib, в которой будут находиться файлы:

Создаем папку bin, поскольку фреймворк будет использоваться в качестве инструмента CLI Node:

Начнем с создания файла CLI.

Создаем файл kwuo в папке bin и добавляем следующее:

Переходим к установке и заполнению lib/cli/cli.js.

Этот файл находит папку test, получает все файлы из нее и запускает их.

Однако ни одна из них не определена в этих файлах. Каким образом файлы и функции работают без ReferenceError? Причина в том, что тестовый фреймворк реализовывает функции и устанавливает их как global перед запуском тестовых файлов.

Создаем файл index.js в папке lib:

Здесь мы устанавливаем глобальные переменные и реализуем функции describe , it , expect , afterEach , beforeEach , afterAll и beforeAll :

Затем устанавливаем массивы beforeEachs, afterEachs, afterAlls и beforeAlls. beforeEachs содержит функции, которые вызываются в начале функции it , к которой он прикреплен. afterEachs вызывается в конце it . beforeEachs и afterEachs вызываются в начале и конце describe .

stats собирает статистику каждой функции describe, а curDesc обозначает текущую функцию describe, запущенную для сбора данных тестирования. currIt содержит запущенную в данный момент функцию it, выполняющую сбор тестовых данных.

Также у нас есть функция expect. Она выполняет тестирование:

Функция expect принимает аргумент для тестирования и возвращает объект, который содержит функции matcher. В данном случае она возвращает объект с функциями toBe и toEqual с аргументом, который они используют для сопоставления с аргументом значения, предоставляемым функцией expect. toBe использует === для сопоставления аргумента значения с ожидаемым аргументом. toEqual использует == для проверки фактического значения с ожидаемым. Функции увеличивают переменные passedTests и failedTests в зависимости от результатов теста, а также записывают статистику в переменную currIt. Мы используем только две функции matcher, однако их гораздо больше:

  • toThrow
  • toBeNull
  • toBeFalsy
  • и т. д.

Вы также можете реализовать их.

Функция describe выполняет те же действия, что и it , но вызывает beforeAlls и afterAlls в начале и в конце.

Функция showTestsResults анализирует массив stats и печатает пройденные и неудачные тесты на терминале.

Таким образом, мы реализовали и установили все функции в объект global , чтобы тестовые файлы могли вызывать их без ошибок.

runTestFiles принимает файлы в массиве, просматривает их с помощью метода forEach и использует метод require для запуска каждого файла.

Структура папки kwuo выглядит следующим образом:


Тестирование фреймворка

Попробуем протестировать наш фреймворк с реальным проектом Node.

Создаем папку src и добавляем add.js и sub.js:

Содержимое add.js и sub.js:

Создаем папку test и тестовые файлы:

Файлы спецификации будут проверять функции add и sub в add.js и sub.js:

Запускаем npm run test в командной строке:

Результат теста будет следующим:


Гайд по Nest.js: что это такое и как написать свой первый код главное изображение

Nest.js — самый важный и популярный фреймворк для создания серверных веб-приложений Node.js. В этом большом гайде мы поможем новичкам сделать первый шаг в освоении этого фреймворка для серверного Javascript и расскажем, в чем вообще особенности Node.js.

Для комфортного усвоения этого гайда вам потребуется:

  • Знание JavaScript на среднем уровне
  • Знание основ TypeScript, особенно синтаксиса декораторов
  • Базовые знания Node.js. Желательно, опыт с Express.js
  • Знание базовых понятий MVC (Model-View-Controller)

Также нам понадобится уставленные на компьютер:

Docker мы будем использовать для развёртывания базы данных. Развертывание СУБД в контейнере — один из самых простых способов. Но если вы хотите использовать другой способ, или же работать с уже имеющейся СУБД — нет проблем. В таком случае Docker вам не понадобится.

Прокачивайте свой уровень программирования: На Хекслете есть несколько десятков треков — специальных курсов для опытных программистов, позволяющие повысить уровень компетентности разработчика в разных направлениях.

Введение

Зачем нам нужен фреймворк?

Большинство задач в программировании — типовые. Если не брать каких-то специфических бизнес-кейсов, скорее всего все возможные задачи уже решены другими программистами и не по одному разу. Не зря в среде программистов среде так часто можно услышать шутки о «велосипедостроении». Фреймворки помогают нам писать меньше шаблонного кода и сосредоточиться на том, какую полезную работу должна делать наша программа.

Первым супер-популярным веб-фреймворком для Node.js был express.js. Nest.js значительно расширяет его функциональность, добавляет декларативности, а также помогает разработчику строить приложение в соответствии с лучшими архитектурными практиками.

Для примера мы напишем небольшой блог, в котором можно просматривать и добавлять статьи. Конечно, это будет очень простое приложение, но его будет достаточно для первого знакомства.

Создадим проект

Чтобы каждый раз не приходилось настраивать проект с нуля, разработчики Nest.js вооружили нас консольной утилитой — @nestjs/cli . Установим её глобально:

Теперь в вашей директории с проектами выполним команду nest new , указав имя проекта.

Когда cli закончит свою работу, мы можем перейти в директорию nestjs-getting-started и посмотреть, что получилось:

Структура проекта

npm run start:dev

Запущенное командой npm run start:dev приложение будет перезагружаться каждый раз, когда изменяется исходный код проекта.

Остановить работающее приложение можно комбинацией клавиш Ctrl+C

Точка входа

Точкой входа в приложение на Nest.js, как и в любом другом MVC-подобном фреймворке, являются контроллеры. Пока в приложении имеется один: app.controller.ts:

Обратите внимание, что класс AppController и метод getHello() помечены декораторами @Controller() и @Get() . Nest.js очень широко использует декораторы, поэтому к ним лучше сразу привыкать. Они позволяют писать приложение в более декларативном ключе, указывая что мы хотим сделать, и оставляя детали реализации фреймворку.

Если читатель «плавает» в теме декораторов, рекомендуем к прочтению эту статью.

Соответственно, значение, которое вернётся из метода, будет отправлено в теле ответа. Код ответа по умолчанию для GET-запросов — 200.

Метод getHello() и конструктор мы пока удалим. Взамен создадим метод index() , который будет возвращать статьи. Контроллер приобретёт такой вид:

Обратите внимание, что мы возвращаем не «голый» массив, а заворачиваем его в объект c ключом articles . Нам это пригодится чуть дальше.

Статьи

Пришло время наполнить блог статьями. Но что такое «статья»? Каждая статья — объект, который будет иметь уникальный идентификатор, заголовок и контент.

Создадим в папке src новый файл article.model.ts и в нём опишем класс статьи:

Почему id — необязательный параметр, выяснится позже — когда будем подключать базу данных.

Теперь представим, что у нас в блоге уже есть парочка статей. Создадим файл articles.ts:

Осталось вернуть массив статей из метода index() в контроллере:

Если теперь запустить приложение, мы увидим в браузере следующее:

Добавляем HTML-шаблоны

Для того, чтобы пользователю было комфортнее работать с сайтом, сервер должен вернуть браузеру ответ не в формате json, а в виде html-страницы.

Создадим в корне проекта директорию views — в ней мы будем размещать HTML-шаблоны.

Устанавливаем шаблонизатор

Nest.js по умолчанию не устанавливает никаких движков шаблонизации, так как не знает, какой именно по душе пользователю. Мы для этого примера возьмём pug (ранее известный как jade). Этот пакет нам придётся установить самостоятельно:

Чтобы подключить pug, перепишем функцию bootstrap ( main.ts ):

Первый шаблон

Наш первый шаблон index.pug будет выглядеть так:

Добавление стилей выходит за рамки этой статьи, поэтому оставим это на усмотрение читателя.

В контроллере добавим к методу index декоратор @Render() (импортируется из пакета @nestjs/common ):

Готово. Теперь при запуске приложения мы увидим то, что и ожидали — html-страницу с двумя статьями.

Добавляем просмотр статей

В app.controller.ts создадим новый метод:

При помощи декоратора @Param() мы можем достать этот идентификатор из URL, преобразовать его к числу (ParseIntPipe) и использовать для поиска нужной статьи.

Кроме декоратора @Param() в Nest.js есть также декораторы @Query() для query-параметров и @Body() для тела запроса.

Добавим в папку views шаблон article.pug:

Шапка будет та же самая, только в тэге title мы выведем название статьи. В теле страницы — ожидаемо title и content , а также ссылка «назад» — на главную страницу.

Осталось починить ссылки на главной странице:

Добавляем статью

Метод getForm() не требует никакого возвращаемого значения — он просто возвращает статический HTML.

Шаблон create-article.pug будет выглядеть так:

Подключаем базу данных

Пока наш блог хранит статьи в оперативной памяти, они будут теряться при каждом перезапуске приложения. Чтобы избавиться от этой проблемы, нам нужно организовать долговременное хранение данных.

Мы используем для этого PostgreSQL — самую популярную на сегодня свободную СУБД. Проще всего поднять базу данных локально при помощи Docker:

Когда Docker закончит свою работу и контейнер с базой данных запустится, можно заняться подключением. Для работы с базой мы будем использовать ORM (Object-Relational Mapping System) под названием Typeorm. Эта система также как, и Nest.js, основана на декораторах, и хорошо интегрируется с фреймворком.

Установим необходимые пакеты:

Теперь мы можем подключить базу данных к проекту.

Особое внимание следует здесь уделить параметру synchronize: true . Эта настройка означает, что при каждом запуске приложения схема БД будет приобретать ту форму, которую мы описываем в коде (классы, помеченные @Entity ). На этапе активной разработки приложения это очень удобная возможность.

Функционально мы пока ничего в приложении не поменяли, но при старте, если подключение БД прошло успешно, мы увидим в консоли несколько SQL-запросов.

Переносим статьи в базу данных

Перепишем файл article.model.ts:

Здесь мы видим несколько новых декораторов:

  • @Entity() — означает, что в базе данных нашей модели соответствует таблица.
  • @Column() — полю класса соответствует поле в таблице
  • @PrimaryGeneratedColumn() — автоматически генерируемый идентификатор, которой будет в нашей таблице первичным ключом.

Поскольку у нас включена синхронизация, в БД уже создалась таблица articles. Вы можете убедиться в этом при помощи любой утилиты для работы с БД, например DBeaver или DataGrip.

Последний штрих: удалим файл articles.ts и перепишем app.controller.ts так, чтобы он использовал базу данных:

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

Заключение

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

Чтобы освоить всё это, а также более продвинутые возможности Nest.js, читателю предстоит пройти немалый путь, и автор надеется, что эта статья станет уверенным первым шагом на нем.

Весь код учебного проекта, который мы использовали в этой статье в качестве примера, вы можете посмотреть здесь.

Никогда не останавливайтесь: В программировании говорят, что нужно постоянно учиться даже для того, чтобы просто находиться на месте. Развивайтесь с нами — на Хекслете есть сотни курсов по разработке на разных языках и технологиях.

Читайте также: