ZeroNet Blogs

Static ZeroNet blogs mirror

GomZik's Blog

Русскоязычный блог о ZeroNet и не только глазами разработчика

Совсем маленькая заметка о том, как сделать наш роутер еще немного лучше!


Посмотрев на этот кошмар:

<a :href="router.url('test', {param: 123})" @click="router.go('test', {param: 123}, $event)">Test link</a>

Понял, что это очень сильно отличается от того, что я вижу при использовании Vue-router в обычном вебе и решил немного поправить данную несправедливость.

Добавим файл components/router_link.js:

import router from 'router.js'

function guardEvent(event) {
  if (event && (event.ctrlKey || event.metaKey)) return
  if (event) event.preventDefault()
  return true
}

export default {
  name: 'router-link',
  props: ['to', 'params'],
  render: function(h) {
    console.log(this.to, this.params)
    let on = {
      click: event => {
        if (guardEvent(event)) {
          router.go(this.to, this.params)
        }
      }
    }
    const data = {
      on, attrs: {href: router.url(this.to, this.params)}
    }
    return h('a', data, this.$slots.default)
  }
}

А так же уберем строки из метода go нашего роутера:

  // В сигнатуре метода стоит убрать параметр event
  // ...
  if (event && (event.ctrlKey || event.metaKey)) return
  if (event) event.preventDefault()
  // ...

Далее в наши компоненты мы заимпортируем наш свежеиспеченный router_link.js и начнем использовать его в разметке:

import RouterLink from 'components/router_link.js'
// ...
export default {
  // ...
  components: { // ...
    RouterLink
  }
}
<router-link to="test" :params="{param: 123}">Test Link</router-link>

Ну ведь гораздо приятнее же, не так ли?

PS

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

Итак, в прошлом уроке мы сделали самый-самый простенький роутер, в этом мы попробуем превратить его в крутой роутер с интеграцией с ZeroFrame API.

Для начала - давайте определим, чего нам не хватает:

  • При переходе со страницу на страницу, URL в строке браузера не меняется
  • Нельзя по прямому URL перейти на необходимую страницу
  • Не хватает параметризации URL

Что ж, сегодня попробуем это все поправить. Приступим?


Начнем с того, что реализуем клик по ссылке, который меняет URL в строке браузера, одновременно с этим мы сделаем параметризированные URL'ы

Для этого идем в наш src/router.js и модифицируем его следующим образом:

// ...
import pathToRegexp from 'path-to-regexp'

import ZeroApi from 'zero_api.js'
// ...

const router = new Vue({
  // ...
  data: {
    //...
    routeParams: null
  },
  methods: {
    go: function(to, params = {}) {
      const url = this.relativeUrl(to, params)

      /**
       * Инструкция для ZeroFrame API, которая позволяет поменять URL браузера
       * В качестве параметров можно передать объект-состояние, который поможет
       * восстановить страницу при нажатии кнопки "Назад" в браузере
       * (например {scrollTop: 100}), вторым параметром - тайтл новой страницы
       * третьим - урл новой страницы.
       * Интерфейс аналогичен соответствующему методу в HTML5 History API
       * ZeroNet wrapper в итоге установит урл в строке браузера вида
       * <https://www.zerogate.tk/<zite_address>/?<url>>
       */
      ZeroApi.api().cmd('wrapperPushState', [{}, '', url])
      this.route(url)
    },

    relativeUrl: function(to, params = {}) {
      const route = this.routes.find(x => x.name === for_route)
      const url = pathToRegexp.compile(route.path)(params)
      return url
    },

    route: function(url) {
      let matchedRoute = null
      let params = null

      // Находим подходящий route, устанавливаем соответствующий компонент
      // Сохраняем внутри роутера параметры.
      for (const route of this.routes) {
        let keys = []
        const re = pathToRegexp(route.path, keys)
        const result = re.exec(url)
        if (result !== null) {
          matchedRoute = route
          params = {}
          keys.forEach((item, index) => {
            params[item.name] = result[index + 1]
          })
          break
        }
      }

      if (matchedRoute) {
        this.currentView = matchedRoute.component
        this.routeParams = params
      }
    }
  }
}
// ...

Для эксперимента, сделаем особую ссылку с параметрами на Library, а в Library попытаемся их считать. В верху нашего src/router.js

const routes = [
  // ...
  {path: '/test/:param', name: 'test', component: 'library'} // А может это уже было добавлено в прошлом уроке?
]

Делаем ссылку внутри src/components/home.vue

<a href="javascript:;" @click.prevent="router.go('test', {param: 123})">Test link</a>

А внутри src/components/library.vue:

<!-- Используем $data для доступа к raw объекту, без навешанных сеттеров-геттеров Vue -->
<pre>{{ router.$data.routeParams }}</pre>

отобразим параметры, которые пришли из router. Теперь можем и поиграться с нашими ссылками. Обращаем внимание на то, что ссылки в браузере меняются. Однако, стоит нам обновить страницу используя F5 (кнопку обновить), мы, вне зависимости от того, где находились, снова придем на главную. К тому же, если в браузере нажать кнопку назад, мы также не вернемся на предыдущую страницу. Давайте же исправим это.

Снова идем в наш src/router.js:

const router = new Vue({
  data: {/*...*/},

  computed: {
    apiConnected: function() {
      return ZeroApi.connected
    }
  },

  watch: {
    apiConnected: function(val) {
      this.onConnect(val)
    }
  },

  created: function() {
    this.onConnect(this.apiConnected)
  }

  methods: {
    // ...
    // Как только ZeroFrame связался с ZeroNet сервером, мы определяем текущий
    // URL, переходим на нужную страницу, а так же подписываемся на событие
    // которое означает, что пользователь нажал кнопку назад в браузере.
    onConnect: function(isConnected) {
      if (!isConnected) {
        return
      }

      this.route(this.getUrl(base.href))

      ZeroApi.api().subscribe('wrapperPopState', (msg) => {
        this.route(this.getUrl(msg.params.href))
      })
    },

    getUrl: function(fullUrl) {
      let url
      if (fullUrl.indexOf('?') === -1) {
        url = ''
      } else {
        url = fullUrl.replace(/.*?\?/, "")
      }
      return url
    }
  }
})

Теперь можно не только поиграть с сылками, но и с кнопками "Назад" и "Обновить".

В качестве бонуса

Очень не хватает возможности нажать на ссылку правой кнопкой и нажать что-то вроде "Открыть в новой вкладке", такое же поведение есть при нажатии на среднюю кнопку мыши (колесико), такое же поведение есть при нажатии на ссылку обычным кликом с зажатым ctrl.

На самом деле, для этого нужно сделать две вещи:

  • Подставить в <a href=""> настоящую ссылку
  • Изменить @click обработчик, чтобы передать событие браузера в метод go, чтобы мы могли определить, нажата ли кнопка ctrl

В последний раз в этом уроке перейдем в src/router.js и добавим туда следующее:

const router = new Vue({
  data: {/*...*/},
  // ...
  methods: {
    // ...
    go: function(to, params={}, event) {
      // Если зажат ctrl или meta (насколько я знаю, это так работает в Mac-ах
      // работаем так, как обычно работает браузер
      if (event && (event.ctrlKey || event.metaKey)) return
      // Отменяем дефолтное поведение (переход по ссылке)
      if (event) event.preventDefault()

      // Весь остальной код метода
      // ...
    },
    // ...
    // Данный метод вернет нам полный URL, включая часть с 
    // <https://www.zerogate.tk/<zite_address>/?/<url_to_route>>
    url: function(for_route, params = {}) {
      const url = this.relativeUrl(for_route, params)
      const absoluteUrl = base.href.split(/[?#]/)[0] + '?' + url;
      return absoluteUrl
    }
  }
})

Теперь нам нужно пройтись по всем местам, где есть ссылки и поменять их на что-то вроде:

<a :href="router.url('test', {param: 123})" @click="router.go('test', {param: 123}, $event)">Test link</a>

Т.е. меняем @click.prevent="router.go(...)" на @click="router.go(..., $event)", а так же изменить значение href, сделав его привязанным :href="router.url(...)". Все, теперь можно открывать ссылки в новой вкладке, так, как будто перед нами самый обычный сайт.

На текущий момент, разработка зайта ведется по адресу http://localhost:43110/1JMNrd9FD19AhWVukYJ2gdxAiK2ohczwk5/, там уже реализовано какое-то подобие плеера, однако оно сразу начинает проигрывать, будьте внимательны.

Внимание, вопрос! Рассказывать ли в следующем уроке о создании плеера, учитывая, что там нет ничего, что связанно с ZeroNet и взаимодействием с ZeroFrame API?

Мы собираемся писать довольно большое приложение. В планах конечно же будет сам проигрыватель, возможность добавить музыку в общую базу, обязательно хочу добавить функцию "Моя библиотека", куда и будут попадать любимые треки пользователя. Любой плеер так же должен иметь возможность управлять плейлистами. Ну и конечно же, мы должны иметь возможность дать ссылку на трек другому пользователю ZeroNet. В общем без роутера нам не обойтись. Как я уже упоминал, Vue-router нам не подходит из-за специфики работы ZeroNet. В этой статье мы и попробуем решить проблему раутинга своими силами.


Для того, чтобы сделать свой набросок роутера, я изучал как делает раутинг в своих проектах Nofish, а так же изучал, как именно работает стандартный роутер Vue-router. Попробуем собрать это все воедино. Но сначала небольшая подготовка. Сделаем два небольших компонента, чтобы тестировать наш роутер. Пусть они будут называться Home, где в планах мы сделаем что-то для открытия новой музыки (может быть для начала - последние поступления в базе) и Library, где, как уже было отмечено выше, пользователь будет управлять своей музыкой (все по взрослому, да :)). Пока они будут максимально простыми и пустыми, к ним мы обязательно вернемся в более поздних статьях.

Итак, создаем файл src/components/home.vue с простым содержимым:

<template>
  <div>
    <h1>Home</h1>
  </div>
</template>

<style scoped>
</style>

<script>
export default {
  data: () => {
    return {}
  }
}
</script>

Добавим его вместо нашего Hello, world'a в src/components/app.vue:

<template>
  <div>
    <home></home>
  </div>
</template>

<style scoped>
</style>

<script>
import Vue from 'vue'
import ZeroApi from 'zero_api.js'

import Home from './home.vue'

export default {
  data: () => {
    return {
      api: ZeroApi
    }
  },

  components: {
    Home
  }
}
</script>

Обновив страницу, увидим надпись большими буквами Home.

Теперь проделаем тоже самое с компонентом Library, src/components/library.vue:

<template>
  <div>
    <h1>Library</h1>
  </div>
</template>

<style scoped>
</style>

<script>
export default {
  data: () => {
    return {}
  }
}
</script>

И также можем добавить его в наш src/components/app.vue:

<...>
<home></home>
<library></library>
<...>
<script>
<...>
import Library from './library.js'
<...>
components: {
  Home, Library
}
<...>

Итак, теперь у нас есть два компонента, между которыми мы хотели бы переключаться, приступим же к роутеру. Для начала установим path-to-regexp, который используется для парсинга путей в Vue-router, дабы не заниматься какими-то непонятными парсингами. В терминале, в корне проекта набираем:

npm install --save path-to-regexp

Сделаем набросок нашего роутера, создаем src/router.js

const routes = [
  {path: '', name: 'home', component: 'home'},
  {path: '/my', name: 'library', component: 'library'},
  {path: '/test/:param', name: 'test', component: 'library'} // Это для тестирования параметризованных урлов.
]

const router = new Vue({
  data: {
    currentView: 'home', // Имя компонента, которое активно на текущий момент
    routes
  }
})

export default router

Это лишь набросок, а не работающий роутер, пробуем так сказать идти сверху, как бы мы им пользовались.

Давайте теперь заставим наш App выводить лишь активный компонент. Добавим router в секцию data нашего приложения

// ...
import router from 'router.js'
//...
export default {
  data: () => {
    return {//...
      router
    }
  }
}

и заменим в темплейте приложения наши два новых компонента на такую конструкцию

<!-- ... -->
<!-- <home></home>
</library></library> -->
<keep-alive>
  <component :is="router.currentView"></component>
</keep-alive>
<!-- ... -->

Обновив страницу увидим лишь Home и более ничего. Попробуем сделать ссылку на Library. Идем в src/components/home.vue и проделаем такие вещи (представим, что наш роутер уже работает):

<template>
  <div>
    <h1>Home</h1>
    <a href="javascript:;" @click.prevent="router.go('library')">Library</a>
  </div>
</template>

<style scoped>
</style>

<script>
import router from 'router'

export default {
  data: () => {
    return {
      router
    }
  }
}
</script>

Конечно же, работать сейчас ничего не будет, но давайте попробуем сделать что-то простое, чтобы заработало! Возвращаемся в router.js и пробуем заимплементировать метод go

// ...
const router = new Vue({
  data: { /*...*/ },
  methods: {
    go: function(to) {
      const matchedRoute = this.routes.find(x => x.name === to)
      if (matchedRoute !== undefined) {
        this.currentView = matchedRoute.component
      }
    }
  }
})
// ...

Вот таким вот простым способом мы уже добавили интерактивность в наш зайт. Для тестов сделаем обратную ссылку из Library на Home (справитесь без вставки кода?) и можем уже обновить страницу и поиграться с этими переходами от Home к Library и обратно.

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

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

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

Можно было бы сделать еще лучше, и перенести папку с проектом куда-то в отдельное место, оставив в папке с ZeroNet лишь симлинку (справедливо для *nix-based систем). Делается это просто

cd <path to zeronet>/data/
mv <our site address> ~/prg/zeronet/
ln -s /home/<user>/prg/zeronet/<our site address> ./

Таким образом мы вполне себе можем инициализировать например тот же git репозиторий в этой папке, однако не забудьте добавить .git в ignore ключ файла content.json

А теперь начнем восстановление стандартной страницы с использованием наших "современных" штук.


Перенесем ZeroFrame.js в папку src

Для начала нам стоит сделать ZeroFrame.js доступным из нашего проекта через import. Для этого сделаем пару модификаций, перенесем файл js/ZeroFrame.js в src/ZeroFrame.js, а так же слегка модифицируем его, добавив в самый конец одну единственную строчку

export default ZeroFrame

Создадим VueJS приложение

Создадим новый файл, например src/components/app.vue и опишем наше Hello, world! приложение:

<template>
  <div>
    <h1>Hello, world</h1>
  </div>
</template>

<style scoped>
</style>

<script>
import Vue from 'vue'

export default {
  data: () => {
    return {}
  }
}
</script>

А теперь, что бы что-то заработало, нам нужно сделать две вещи.

Идем в src/index.js и пишем там всего пару строк


import Vue from 'vue' import App from 'components/app.vue' const app = new Vue({ render: h => h(App) }).$mount('#app')

Указываем VueJS где будет располагаться наше приложение. Так же нам нужно создать соответствующий элемент в нашей изначальной верстке, т.е. в index.html

Во первых там стоит убрать все теги <script> кроме того, что мы написали в прошлый раз, а также добавить <div id="app"></div>. Ваш итоговый html файл должен выглядеть следующим образом:


<!DOCTYPE html> <html> <head> <title>New ZeroNet site!</title> <meta charset="utf-8"> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <base href="" target="_top" id="base"> <script>base.href = document.location.href.replace("/media", "").replace("index.html", "").replace(/[&?]wrapper=False/, "").replace(/[&?]wrapper_nonce=[A-Za-z0-9]+/, "")</script> </head> <body> <div id="app"></div> <script type="text/javascript" src="js/all.js"></script> </body> </html>

На текущий момент, вызвав ./node_modules/.bin/webpack (а можно запустить его в watch режиме используюя ./node_modules/.bin/webpack -w), а после, зайдя на свой зайт, мы легко должны увидеть надпись Hello, world!.

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

Подключаемся к ZeroNet API

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

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

Так что создадим свой src/zero_api.js, где и будем делать, так сказать, слой совместимости. Импортируем оригинальный ZeroFrame.js, наследуемся от класса, добавляем необходимые методы и оборачиваем это в инстанс Vue объекта.


import ZeroFrame from 'ZeroFrame.js' import Vue from 'vue' class App extends ZeroFrame { init() { this.subscribers = {} } setSiteInfo(siteInfo) { apiWrapper.siteInfo = siteInfo } onOpenWebsocket() { apiWrapper.connected = true this.cmd("siteInfo", [], siteInfo => { this.setSiteInfo(siteInfo) }) } onRequest(cmd, msg) { if (this.subscribers[cmd] !== undefined) { this.subscribers[cmd].forEach(cb => { cb(msg) }) } else { console.log('No listers for', cmd, msg) } } subscribe(cmd, cb) { if (this.subscribers[cmd] === undefined) { this.subscribers[cmd] = [] } this.subscribers[cmd].push(cb); } } const app = new App() const apiWrapper = new Vue({ data: { connected: false, siteInfo: null }, computed: { siteModifiedDate: function() { return new Date(this.siteInfo.content.modified*1000) } }, created: function() { app.subscribe('setSiteInfo', msg => { this.siteInfo = msg.params }) }, methods: { api: () => app, } }) export default apiWrapper

Пока так, криво и коряво, думаю чуть позже это все мы исправим, когда столкнемся с уже действительно частым и серьезным использованием ZeroFrame API

Наверное стоит оставить немного пояснений: как только ZeroFrame сообщает нам, что он готов к использованию мы запрашиваем информацию о нашем зайте, а как только ее получим, запишем эту информацию в объект Vue, да бы иметь возможность использовать это информацию "реактивно" (т.е. как только она по каким-либо причинам обновится, благодаря самому Vue, все приложение об этом сразу узнает). Из модуля мы уже вернем инстанс Vue, но на всякий случай дадим ему возможность вернуть оригинальный ZeroFrame инстанс, позже придумаем, что с ним делать :)

За деталями имеет смысл читать оригинальную документацию. Она конечно не фонтан, но какую-то информацию предоставляет.

Что ж, осталось всего ничего, отобразить полученную информацию о зайте на странице. Для этого идем в наш src/components/app.vue и модифицируем его следующим образом:

<template>
  <div>
    <h1>Hello, world</h1>
    <div>{{ api.connected ? 'Connected' : 'Disconnected' }}</div>
    <template v-if="api.siteInfo !== null">
      <h2>Site Info</h2>
      <p>Page address: {{ api.siteInfo.adress }}</p>
      <p>- Peers: {{ api.siteInfo.peers }}</p>
      <p>- Size: {{ api.siteInfo.settings.size }}</p>
      <p>- Modified: {{ api.siteModifiedDate }} </p>
    </template>
  </div>
</template>

<style scoped>
</style>

<script>
import Vue from 'vue'

import ZeroApi from 'zero_api.js'

export default {
  data: () => {
    return {
      api: ZeroApi,
    }
  }
}
</script>

Обновив наш зайт, мы увидим вcю ту же информацию, что видели при создании нового зайта. В качестве эксперимента, можно потянуть за "нолик" справа вверху, изменить Title или Description, сохранить и в течении пары секунд мы увидим, как Modified поле изменится на более актуальное.

На этом на сегодня все. В следующем уроке мы с вами начнем наконец двигаться к цели и делать что-то на пользу нашему приложению. Например напишем роутер, так как Vue-router, как оказалось, немного не готов для использования внутри iFrame, заменяет GET параметры, которые ZeroNet прокидывает в целях безопасности. К тому же, url в ZeroNet меняется иначе, нежели в обычных веб-приложениях.

Да, я уже пробовал что-то сделать с нуля. Да я уже пробовал рассказывать об этом в блоге. Ну и что. Попробую еще раз! Мой блог, что хочу, то и делаю!

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


Я по разным личным причинам немного связан с музыкой, потому и решил, что можно было бы попробовать написать музыкальный плеер, что-то похоже на Google Play Music и подобных. Почему-то на первый взгляд кажется, что использование Site Merger (Та самая штука, которая используется в ZeroMe) будет вполне себе уместно, ведь я планирую, чтобы каждый пользователь мог добавить в базу любую музыку. Сюда же конечно же войдет и авторизация (пользовательский контент иначе не сохранить, насколько я понимаю). Так же мы коснемся фичи опциональных файлов, не будем гонять всю возможную музыку по всему ZeroNet. Это конечно может вызвать проблемы с доступностью некоторых песен, но посмотрим что выйдет в конечном итоге.

Вообще странное дело. Я вроде как нахожусь по ту сторону баррикад, где хочется, чтобы музыку покупали, а сейчас собираюсь положить начало ~~удобному~~ (не факт) плееру без правообладателей. Наверное это потому что ту музыку, которую я выпускаю (не путать с исполняю, я на самом деле не музыкант) доступна бесплатно в MP3 320kbps, а вот уже FLAC раздаю за денюшку. Так что вроде как и так музыка бесплатная. В общем тут можно долго размышлять на тему пиратства, контента и прочего и да, должен сказать, что молодые артисты живут бедно, но я не думаю, что от зайта в зеронете вообще кто-то сильно пострадает.

Вообще я думаю, что по аналогии кто-нибудь может сесть и пилить рядом зайт с фильмами или сериалами (эдакий обновленный Play, что блуждал тут какое-то время назад, он вообще актуален сейчас?), буквально проходя данный цикл статей, это уже оставлю на откуп читателям.

Вижу уже сейчас несколько препятствий на пути к поставленной цели. Документации по мерджеру совсем нет. Если есть, то в руки она мне не попадалась, поэтому если вы знаете какие-либо ссылочки, будет очень полезно!

Ну что ж, хватит лить воду, поехали!

Создаем новый зайт

Благодаря нашему всеми любимому Nofish, это теперь вообще крайне просто! Нужно лишь зайти на домашнюю страницу ZeroNet (он же ZeroHello), нажать на три точки и выбрать кнопку Create new site

_______________2017-05-26_21-15-02.png (400x574)

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

Инициализируем окружение

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

Ставим webpack, babel, vuejs и все надлежащие штуки

npm init -y
npm install --save babel-core babel-loader babel-polyfill babel-preset-es2015 css-loader vue vue-loader vue-template-compiler webpack

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

Подготовим специфичный для ZeroNet файл content.json

По большей части, на текущий момент нам нужно сделать так, что бы наши девелоперские штуки не попадали в папки к конечным пользователям. Добавим все наши непонятные штуки в ключ ignore в content.json

{
...
"ignore": "((node_modules/.*)|(webpack.config.js)|(package.json)|(src/.*))",
...
}

Я храню исходные js файлы в папке src, однако конечного пользователя будет интересовать только собранные файлы. Поэтому мы исключаем папку src. Так же уберем из раздачи все, что связанно с папкой node_modules (туда npm поставил все наши зависимости), package.json (файл, в котором описаны все наши зависимости) и webpack.config.js (об этом файле читайте ниже).

Подготовим проект к сборке

Так выглядит мой webpack.config.js. Опять же, вы можете прочитать о настройке в ранних статьях (внимание, немного отстало от реальности), или же на официальном сайте тыц


module.exports = { entry: ['babel-polyfill', __dirname + '/src/index.js'], output: { path: __dirname + '/js', filename: 'all.js' }, module: { loaders: [{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: ['es2015'] } }, { test: /\.vue$/, loader: 'vue-loader' }] }, resolve: { modules: [ 'src', 'node_modules' ] } }

Создадим наш главный файл в src/index.js, можно для теста написать там

console.log('Hello, world!')

чтобы проверить работоспособность, выполним команду

./node_modules/.bin/webpack

В итоге в папке js/ у нас появится файл all.js, в котором будет собран наш клиентский код, который и надо раздавать всем пирам.

Подключим собранный файл на страницу

Открываем index.html и добавим туда для начала строчку

<script type="text/javascript" src="js/all.js"></script>

В общем, подключаем наш собранный файл.

В следующей статье, я расскажу, как сделать ту же самую страницу, что была по-умолчанию, только с использованием всех наших прибамбасов и используюя VueJS

PlantUML

- Posted in GomZik's Blog by with comments

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

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

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


Впервые я познакомился с этим инструментом около года назад. Восторга моему не было предела. После выпуска из универа, где-то раз в год я вспоминал, что нас учили проектированию с помощью UML. Конечно, пока я был неопытным разработчиком, рисовать диаграммы особо не приходилось, однако интерес к "академическому" проектированию ПО иногда просыпался. И вот я, такой весь из себя специалист, начинал искать инструменты для создания UML. Да ещё так, чтобы это работало под Linux. И что бы понятно и удобно.

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

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

Короче говоря, после некоторых попыток я бросал это занятие и забывал об этом.

А через год все по новой.

Однако где-то год назад, один добрый человек показал мне PlantUML. В первые несколько дней у меня было ощущение, что Интернет сговорился против меня и усердно скрывал эту штуку в самых недрах. Не индексируя. Не говоря об этом на StackOverflow. Никакого, как мне казалось, упоминания.

Это, в общем, такая тула, которая конвертирует вашу ASCII псевдографику в картинки. Тут проще показать пример. Предоставлю вам пару картинок и пару исходников для наглядности.

@startuml
actor "User" as user
participant "Service" as service
participant "3rd party" as tp


user -> service : HTTP GET
activate service
service -> service : Do some work
service -> tp : Call webhook
activate tp
service -> user : HTTP 200 OK
deactivate service
tp -> tp : Do some background job
tp -> service : Callback
deactivate tp
activate service
service -> service : Change state
service -> tp : HTTP 200 OK
deactivate service
@enduml

Example 1

Вот таким вот не сложным образом мы получаем так называемую Sequence Diagram.

Ещё пример:

@startuml
class "BaseClass" as bc {
    - private_field: integer
    + public_field: string
    + {static} static_method()
    + {abstract} say_hi(name: string)
}

class "InheritedClass" as ic {
    + say_hi(name: string)
}

ic --|> bc
@enduml

Example 2

И вот перед нами диаграмма классов.

Самое прекрасное, что это все можно положить под контроль версий (git например) и там будет видны изменения! Можно подключить это все добро в автоматический генератор документации.

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

Спасибо за внимание!

Созываю русскоязычное сообщество вернуться в мир ZeroNet. Скоро должна появится первая социальная сеть на технологиях ZeroNet, чего я так сильно и жду. В связи с этим, хочу как-то вернуть русскоязычное сообщество в сеть.

В планах есть вернуться к туториальным постам про ZeroNet. С обновленным ZeroTalk пока все заглохло, поэтому я думаю, что могу выделить немного свободного времени для оживления этого блога.

Сумбурная заметка под пивом. Простите :)

Я не умер

- Posted in GomZik's Blog by with comments

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

Первая часть тут, чуть ниже, но для ленивых - вот: Как я готовлю свой проект

Сегодня мы поговорим про то, как к нашему добру подключить Vue.js


Но для начала поговорим о том, что такое Vue.js. Точнее скорее мы поговорим о том, что такое Vue.js моими глазами.

Как я уже упоминал, я в первую очередь python-developer, который занимается в основном web'ом. Ну и по долгу службы приходится часто работать с JavaScript. Я прошел положенные пять стадий до тех пор, пока не смог его даже полюбить. Так вот, пика этой любви я достиг познакомившись с Vue.js.

Однако, как и все (ну или действительно очень многие), я начинал с jQuery, спаггети-кода, колбэчного ада и вот этого вот всего. Если вы как раз на этой стадии, то думаю вам стоит вдумчиво дочитать эту статью до конца.

В конце концов, после очередной задачи на работе, я понял, что мне это все порядком надоело, код стал очень тяжело модифицируем, искать причину бага тоже становилось все тяжелее и тяжелее. Я тогда только впервые услышал про React.js да и он относительно недавно появился и только начинал вызывать бурление масс. С мыслью о том, что люди живут как-то проще, что им не нужно программировать пошагово, как из одного состояния перейти в другое, причем когда их штук 5 и все они друг в друга могут переходить между собой я обратился за помощью к другим командам в компании, где были именно специалисты frontend разработки.

Передо мной стояла задача - облегчить жизнь себе и своим коллегам, а так же уметь работать (на тот момент еще) с IE7+. И да, мне очень подкупал на тот момент React. Выслушав мою боль и мои требования, ребята почти хором сказали: "Тебе нужен knockout.js".

И вот знаете - мне понравилось! Писать код стало заметно проще. Заметно проще стало и понимать, что происходит не так. Были разве что пару неочевидных моментов в этом knockout'е, да и то, если вдумчиво читать (а с моим английским это иногда бывает трудно) то это будет на поверхности (или почти). Ну вот из-за всяких мелких нюансов я и начал поглядывать на то, что еще в мире JS существует.

Ну и как-то ненароком наткнулся я на Vue.js. Совершенно случайно. Листая какую-то связанную статью на хабре. В комментариях. Тогда оно еще было версии 0.12 или что-то около того. Потыкав на дому я понял, что мне оно нравится, однако требование к браузерам в виде IE9+ не позволило мне его попробовать в реальных проектах. Так оно и осталось в загашнике того, что я хочу обязательно попробовать.

Релизнулась версия 1.0 и я понял, что дальше тянуть нельзя. Работу я уже сменил, требования к браузерам стали не такими жестокими. Да и сама MS их всех разом решила похоронить. К сожалению на новой работе я уже успел начать проект, в котором взял уже знакомый мне knockout.js, так что версию 1.0 изучать мне пришлось снова на дому. Особых конкретных отличий я не заметил, но вспомнил, что это то, с чем я хочу работать.

Если коротко, то Vue.js для меня выглядит некоторым компромиссом между knockout.js (с его нюансами для поддержки старых браузеров) и React (который не нравится довольно многим за то, что пропагандирует шаблоны прямо в JS коде). У Vue.js хоть и не самое большое, но очень толковое сообщество, а автор библиотеки настолько радеет за свое детище, что понаписал дополнительных утилит к ней на все случаи жизни. И черт возьми, это просто прекрасно.

Вы можете использовать Vue.js и по старинке, подключив его скриптом к вашему документу, и с помощью ES6, и с помощью ES5, и есть адаптеры к webpack, которые позволяют держать компоненты в удобном, структурированном виде, и подсветка синтаксиса для популярных редакторов имеется (там есть особый формат, который идеально подходит для компонентной структуре, как по мне)

Получилось какое-то очень большое предисловие, но надеюсь я еще не успел вас утомить, ведь мы еще ничего полезного не сделали!

Ну так давайте же наконец попробуем этот ваш Vue.js! Итак, надеюсь вы все еще в директории с нашим проектом из прошлой статьи, а если нет, то уверен, вы быстро туда придете. Ставим Vue.js

$ npm install vue --save

Но одной библиотеки нам пожалуй мало. Раз уж мы решили использовать всю мощь Webpack, то ставим сюда же рядом vue-loader. С ним есть некоторая трудность, ему нужно еще куча всего, чтобы работать правильно. Вот полная команда

$ npm install vue-loader vue-html-loader css-loader vue-style-loader babel-plugin-transform-runtime babel-runtime@5 --save

babel-runtime умышленно используется 5-ой версии (объяснение).

Ну вот такие вот зависимости. Скорее всего автор просто очень радеет за гибкость и за то, чтобы многое можно было при желании заменить на что-то другое. Например, если мы будем для каскадных стилей использовать какой-то препроцессор (например stylus), нам достаточно будет установить его

npm install stylus stylus-loader --save

и... Ну в общем сами все увидите, а то сложно объяснять, непонятно что.

Так же мне нравится идея, которую продвигает facebook рядом со своим React.js про паттерн flux. Довольно удачная идея, которая получила одну из реализаций от автора библиотеки Vue.js, и называется Vuex. Нам она тоже понадобиться для демонстрации.

$ npm install vuex --save

Итак, давайте же уже писать код!

Нам нужна небольшая правка в нашем webpack.config.js

module.exports = {
  //...
  module: {
    loaders: [
      {test: /\.js$/, exclude: /node_modules/, loader: 'babel'},
      {test: /\.vue$/, loader: 'vue'}
    ]
  }
}

Говорим webpack'у, что файлы, которые оканчиваются на .vue мы должны грузить с помощью специального лоадера vue-loader.

Зачем же мы все это делаем. Сейчас маленький примерчик и я начну объясняться. Очистим все, кроме самой первой строчки (напомню, это import 'babel-polyfill') в нашем файле assets/index.js. Создадим файл assets/App.vue

Тут вы немного сейчас удивитесь. Пишем туда следующее

<script>
import MyButton from 'components/Button.vue'
import MyCounter from 'components/Counter.vue'
import store from 'store.js'

export default {
  computed: {
    counter() {
      return store.state.counter
    }
  },

  methods: {
    buttonClicked() {
      store.actions.increment()
    }
  },

  components: {
    MyButton, MyCounter
  }
}
</script>

<template>
  <div class="app">
    <my-button @button-click="buttonClicked">Click me!</my-button>
    <my-counter :counter="counter"></my-counter>
  </div>
</template>

<style lang="stylus">
  body, html
    margin 0
    padding 0
</style>

<style lang="stylus" scoped>
.app
  padding 10px
  border 1px solid black
</style>

Тут мы объявили компонент в рекомендуемом для Vue.js стиле. Здесь одновременно все вместе, что удобно для компонента и в то же время раздельно (а не как в React.js)

Сначала мы объявляем секцию <script /> в которой размещаем наш [ES6 скрипт][1][2] компонента. Ниже идет секция <template /> в котором мы пишем html разметку, связанную с этим компонентом.

Еще ниже мы видим почему-то две секции <style />. Я просто для себя вывел такое правило, что в самом корневом компоненте (App в данном случае) я объявлю глобальные стили, которые накладываются на весь документ, и рядом объявлю стили локальные, которые работают только для этого компонента. За то, каким именно стиль является отвечает атрибут scoped. Его действие мы особенно хорошо заметим чуть позже.

Так же внимательный читатель мог увидеть атрибут lang со значением stylus. Это собственно про то, что я говорил. Нравится вам какой-то другой язык, вы можете использовать его. Тоже самое касается и <template />, и <script />. Можно подставить туда CoffeeScript вполне спокойно (раз уж тут его любят, грех обойти :) ), сделав lang="coffee" (не забудьте установить пакеты coffe coffe-loader)

Посмотрим на секцию <script /> внимательнее. Мы воспользовались предлагаемым автором Vue.js "синтаксическим сахаром" по объявлению компонента. Мы не импортируем библиотеку Vue.js явно и ни от чего не наследуемся. Однако экспортируемый объект должен быть определенной структуры. Подробнее читайте в документации, ссылки я собрал в конце статьи.

Первым делом мы импортируем наши компоненты (которые мы пока еще не создали, ну да ладно), а так же, по идее flux, мы импортируем наш единый store (о нем тоже чуть позже).

Пользуясь сахаром ES6 мы просто перечисляем в атрибуте components объекта наши компоненты.

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

В целом довольно просто.

Следом очередь за <template />. Тут тоже есть несколько особенностей. Во-первых, мы вставляем наши компоненты используя немного модифицированное имя этого самого компонента. Во вторых, возле некоторых атрибутов мы видим странные символы @ и :. Это такие сокращения, которые нам предоставляет Vue.js. Первый используется, чтобы подписаться на события дочернего компонента, а второй, чтобы "прокинуть" свойство из родительского контекста. В данном случае мы прокидываем наше вычисляемое свойство counter.

В целом тут вроде и все. Я приведу код components/Button.vue и components/Counter.vue без комментариев, так как там все даже проще, чем здесь.

assets/components/Counter.vue

<script>
  export default {
    props: ['counter']
  }
</script>

<template>
  <p>{{ counter }}</p>
</template>

<style lang="stylus" scoped>
  p
    font-weight bolder
</style>

assets/components/Button.vue

<script>
  export default {
    methods: {
      buttonClicked() {
        this.$dispatch('button-click')
      }
    }
  }
</script>

<template>
  <button @click="buttonClicked">
    <slot></slot>
  </button>
</template>

<style lang="stylus" scoped>
  button
    padding 5px
    border-radius 4px
</style>

Хотя нет, есть три вещи, на которых стоит остановиться. Во-первых, обратите внимание, как мы бесцеремонно навешиваем стили на теги. Этого конечно даже вместе с Vue.js делать не очень-то стоит, но сделано это нарочно, для демонстрации возможности scoped. Дело в том, что после обработки webpack'ом, мы получим стили следующего вида

button[_v-123124] {
}

а в html у нас будет красоваться не просто <button />, а <button _v-123124>

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

Следующая маленькая вещь, на которую я обращу ваше внимание - это атрибут props у Counter.vue. Если посмотреть еще разок на код App.vue, то вы увидите, что мы передаем свойство, которое не похоже на обычное DOM-свойство. Так вот чтобы его получить, компонент должен сказать, что он принимает это свойство.

И на последок: магический тег <slot /> - используется для того, чтобы вставлять контент, так сказать, сверху. Если мы снова вернемся к коду App.vue, то увидим, что в template в теге <my-button /> внутри мы написали Click me! Так вот этот самый контент и вставляется на место тега <slot />.

Знаю, вы уже утомились, но тут осталось три маленькие детали. Во-первых - магический store.js. Привожу его код

assets/store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    counter: 0
  },

  actions: {
    increment(store) {
      store.dispatch('INCREMENT')
    }
  },

  mutations: {
    INCREMENT(state) {
      state.counter += 1
    }
  }
})

export default store

Одновременно простой и не совсем. Тут мы используем Vuex, реализацию идеи flux от автора библиотеки Vue.js. В общем и целом это обычный объект, только построен он на геттерах и сеттерах и оттого "излучает" события всем подписчикам, коим и является наш App.vue (вспомните вычисляемое свойство). У него также есть особая структура, о которой подробнее вы прочитаете в доках.

Теперь нам нужно все это как-то запустить в нашем assets/index.js

import 'babel-polyfill'

import Vue from 'vue'
import App from 'App.vue'

const app = new Vue({
  el: 'body',
  components: {
    App
  }
})

Всего в пару строк мы создаем наш Vue application, говорим, к какому элементу DOM-дерева мы привязываемся и так же говорим о том, что у нас единственный компонент - App. Также нам нужно немного подправить index.html, чтобы довести это все до рабочего состояния.

Внутрь <body /> вставляем простой тег <app></app> чтобы получилось так

<!DOCTYPE html>
<html>
<head>
</head>
<body>
  <app></app>
  <script src="js/all.js"></script>
</body>
</html>

Не забываем запустить

./node_modules/.bin/webpack

И можем смело открывать index.html в нашем браузере. Обязательно выполните просьбу кнопки и пожмякайте на нее.

Литература на дом

  1. официальный сайт Vue.js, там вы найдете действительно хорошую документацию
  2. документация по vue-loader
  3. документация по Vuex
  4. видео уроки по Redux - еще одна имплементация идеи flux, откровенно говоря я так и не посмотрел, но сообщество рекомендует.

В качестве послесловия

Снова большая статья. Надеюсь вам будет относительно легко ее переварить. Хотя я понимаю, что техническая часть статьи получилась несколько сумбурной и наверняка оставил у вас множество вопросов, но это сделано и нарочно, и нет. Во-первых, если я буду застревать на каждом моменте, это будет долго, скучно (особенно если человек уже скилловый), да и наверное выйдет за рамки всех возможных квот. Да и времени на это наверное убить нужно много. Во-вторых - призываю вас читать документацию, тем более, что она очень хорошая и простая, со всякими там Getting started и прочими туториалами. Ну и в третьих - я всегда рад пообщаться по поводу каких-то конкретных проблем, поэтому смело пишите мне на почту (ссылка слева есть), сюда в комментарии или заходите в наш уютный Ru-ZeroTalk. Я там регулярно обитаю и подписан на все темы и комментарии.

Спасибо за потраченное время!

Что-то похожее на сноски

[1]: У меня после версии 1.0 как-то даже не получилось воспользоваться ES5 синтаксисом, именно по этому я попробовал ES6 (хоть и относился к нему скептически) и больше стараюсь с ним не расставаться. [2]: я не знаю, почему сноски не работают, в подсказке написано, что должно.

Статья получилась заметно больше, чем я ожидал, поэтому ждите второй части

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


Представленный способ в принципе работает для любых проектов, но я упомяну также и ZeroNet-specific вещи.

Итак, заводим проект.

Первое, что я настоятельно рекомендую это использовать unix-like системы. Это может быть любой дистрибутив Linux, MacOS, FreeBSD (чем черт не шутит, но не проверял ничего) или даже виртуалка с Linux в Windows (необязательно целиком вести проект там, но там стоит его собирать).

Второе - это таки завести отдельную директорию под проект. Начитавших всяких там доков можно подумать, что создал директорию под будущий сайт с помощью zeronet.py и там и работать. Однако это не удобно даже банально в силу того, что директория с сайтом - это человеконечитаемая абракадабра.

Отдельная директория также позволит нам более удобно работать с тем же git-ом и т.п.

Нам также нужны node.js и npm

Node.js, если кто не знает, это интерпретатор JavaScript, основанный на базе движка V8, который в свою очередь был разработан в Google (если конечно ничего не поменялось). Npm это менеджер пакетов для того самого node.js (для python'истов - это местный pip).

Итак, у нас есть отдельная директория, пустая. Пусть называется она hello_zero. У нас установлена node.js вместе с npm. Берем в руки терминал (какая ж разработка без него) заходим в hello_zero и начинаем строить скелет нашего проекта. Инициализируйте git (mercurial, etc.) репозиторий, если нужно. Далее нам нужен файл, в который будут записываться зависимости нашего проекта. Для этого наберем

$ npm init

Нас спросят пару вопросов. Обычно я не заморачиваюсь и отвечаю по-дефолту. Скажем так, более подробная информация нужна, если вы делаете какую-то библиотеку, которую будете шарить в сообщество. А так как у нас проект, можно не загоняться.

Далее. Если вы еще не знаете, что такое Webpack, requirejs, browserify, срочно узнайте. Рекомендую Webpack (оффсайт, он как смесь browserify и require.js. Собрал в себе все самое лучшее от обоих. Нужно оно нам, чтобы собирать из аккуратно структурированных js-файлов (которые даже совсем не обязательно написаны на js), кашу в одном файле, как это принято делать, чтобы не нагружать браузер лишними запросам.

Ставим:

npm install webpack --save

Флаг --save говорит, чтобы npm сохранил данную зависимость (и ее версию) в сгенерированный на прошлом шаге package.json

После установки у нас в корне проекта появляется директория node_modules. Там на текущий момент хранится только что установленный webpack. Смело добавляем эту директорию в .gitignore (или что там у нас используется). Чуть позже мы настроим webpack и посмотрим, как это все дело заставить работать.

Настало время определиться с непосредственно структурой проекта. Если это ZeroNet сайт или какое либо другое HTML\JS\CSS only приложение, то советую вам тут же, в корне создать файл index.html, а так же директорию assets. Если с index.html все понятно, то пожалуй поясню, для чего нужна директория assets. Туда я обычно складываю js скрипты и любую другую статику так, как мне удобно, структурировано. Также все эти js'ы не обязательно должны быть js'ами. Они вполне могут быть CoffeeScript'ами или чем там еще люди балуются. Webpack при правильной настройке все скушает, все переварит и выплюнет нам готовый для браузера js скрипт.

В папке assets создайте index.js, оставьте его пустым. Это будет точкой входа. Не пугайтесь, я сейчас все объясню.

index.html мы можем заполнить довольно стандартно

<!DOCTYPE html>
<html>
<head></head>
<body>
  <script src="js/all.js"></script>
</body>
</html>

Тут все просто, кроме того, что мы ссылаемся на не существующий скрипт js/all.js

Пока несуществующий.

Далее мы создадим начальный конфиг для webpack. Создаем в корне файл webpack.config.js со следующим содержимым

module.exports = {
  entry: {
    all: './assets/index.js'
  },
  output: {
    path: './js/',
    filename: '[name].js'
  },
  resolve: {
    modulesDirectories: [
      'assets',
      'node_modules'
    ]
  }
}

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

Я привык называть собранные webpack'ом скрипты - бандлами (bundle). Так вот, в entry мы говорим webpack'у о том, какие бандлы у нас есть (all) и откуда он, так сказать, начинается. webpack во время сборки начнет осматривать этот файл, найдет все импорты и обработает.

В output мы конфигурируем то, куда именно будут складываться наши выходные файлы и какой формат имени они будут иметь. Т.е. наш бандл all после обработки webpack'ом соберется в файл с названием all.js и будет находится в папке js (т.е. js/all.js относително нашей корневой папки проекта). А это именно тот файл, который мы указали в нашем html файле.

Параметр resolve я использую исключительно для удобства. Стандартный способ webpack запросить какой либо модуль - это написать require('путь/до/модуля.js'). Иногда это очень не удобно и получается что-то вроде require('../../../../models/products/fixed_price.js'). Жутко да? Поэтому я сообщаю webpack'у, как искать модули, если он увидел что-то вроде require('util/mymodule.js'). В данном конкретном случае он сначала поищет в папке assets файл util/mymodule.js, если там не найдет, начнет искать в node_modules. node_modules мы оставляем (так оно работает по-умолчанию) для того, чтобы можно было установить например jquery с помощью npm, и обращаться к нему весьма простым

var $ = require('jquery');

Я тут все распинаюсь да распинаюсь. А казалось бы, зачем все так сложно? Зачем мне выпендривться с какими-то webpack'ами, можно ж по старинке, подключить себе на страницу несколько скриптов, да и обращаться к ним через window.$ или какая там у вас библиотека.

Отвечаю:

  1. Наш код в итоге соберется в один файл, что уменьшает количество запросов на сервер (не совсем справедливо для zeronet, но все равно приятно)
  2. Мы могли бы подключить какую-то библиотеку, о которой потом забыли. Решили изменить код и так уж получилось, что эта библиотека не используется больше нигде. Но тем не менее, она продолжает скачиваться нашему посетителю. В случае с webpack'ом мы получаем явное указание зависимостей, которые просто не попадут в итоговый бандл, если они больше не используются. Все счастливы
  3. У нас появляется две особые возможности. Первая - использовать библиотеки, написанные для node.js в браузере (разуметеся, если у них нет какой-то особой работы с файловой системой, процессом и другими штуками, которые в браузере отсутствуют). Вторая - мы теперь можем использовать разные препроцессоры кода, будь то coffeescript или babel (ES6, об этом далее). Так же, если углубиться в доки, можно научиться делать код разбитый на куски, и куски подгружать тогда, когда они действительно нужны (это несколько бесполезно в рамках ZeroNet, по крайней мере пока один файл не занимает очень много мегабайт)
  4. Если совсем постараться, то мы сможем писать unit-тесты на наш код, который не взаимодействует с DOM-деревом и прочими browser-specific штуками. А это уже много!
  5. Ваш window свободен, там теперь не хранятся куча всяких jQuery, underscore и прочих имен, т.к. все это добро работает в замыкании
  6. Вы можете подключить свою итоговую сборку с помощью async аттрибута и не думать о том, в каком порядке у вас загрузятся все ваши библиотеки.

Ну хватит уже разговаривать, давайте посмотрим, что получилось!

Как запускать: просто идем в корень проекта, если вы еще не там, и выполняем

$ ./node_modules/bin/webpack

Webpack немного подумав выдаст вам несколько строк текста. Если вы видите только зеленый текст - все хорошо. Можно смотреть файл js/all.js хотя там должно быть пусто (или пару строк webpack'овского рантайма, это все вам стоит изучить самостоятельно)

Открыв index.html, зайдя в developer tools мы видим, что никаких 404 не произошло, но это скучно довольно.

Давайте попробуем подключить к нашему проекту препроцессор babel, который поможет нам писать с использованием синтаксиса ES6. Ознакомится с синтаксисом и с самим babel можно на официальной странице BabelJS

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

$ npm install babel-core babel-loader babel-preset-es2015 babel-polyfill --save

Да, тут несколько много всего, больше чем на оф. сайте.

Пакет babel-core - обеспечивает базовую функциональность. Подробнее читайте сами, ибо я в подробности уже не уходил.

Пакет babel-loader - специальный пакет для webpack, который позволяет последнему загружать ES6 код и правильно его обрабатывать.

Вообще для webpack есть множество разных loader'ов, но я боюсь я скоро из-за этой статьи привышу стандартную квоту.

Пакет babel-preset-es2015 насколько я понял, babel более универсальная штука, чем просто дать вам возможность писать на ES6, и непосредственно ES6 находится в отдельном пакете. Нужно копать, чтобы до конца понимать

Пакет babel-polyfill. ES6 привносит довольно много всяких штук, которые не поддерживаются даже самыми современными браузерами. Данный пакет поможет исправить эти недостатки и создаст в экосистеме браузера все необходимое для работы. Конкретный пример Symbols.

Фух. Едем дальше. ES6 на этом у нас магическим образом не появится. Нам нужно:

  • Сконфигурировать вебпак Добавьте строки, чтобы получилось так javascript module.exports = { entry: { all: './assets/index.js' }, output: { path: './js/', filename: '[name].js' }, module: { loaders: [ {test: /\.js$/, exclude: /node_modules/, loader: 'babel'}, ] }, resolve: { modulesDirectories: [ 'assets', 'node_modules' ] } } Намекну, что речь про параметр module с внутренним массивом loaders. Тут мы указали, что любой js файл, кроме тех, что лежат внутри node_modules (чаще всего они не ES6 совместимы) нужно обрабатывать babel-loader'ом

  • Сказать babel'у, какой preset использовать. Создаем в корне проекта .babelrc с следующим содержимым ```json { "presets": ["es2015"] }


После этого мы можем писать с использованием ES6. Давайте перейдем в ранее созданный assets/index.js и напишем что-то с использованием нового синтаксиса. Можно попробовать вот такое ```javascript import 'babel-polyfill' // См. выше. Дополняем возможности браузера function iteritems(obj) { return { [Symbol.iterator]: function*() { for (let key in obj) { let value = obj[key] yield [key, value] } } } } let obj = { keyA: 'valueA', keyB: 'valueB' } for (let [key, value] of iteritems(obj)) { console.log(key, value) }

Демонстрирует сразу несколько возможностей и обычных изменений в ES6 по сравнению с ES5. Тут и генераторы, и итераторы, и Symbols, и новое объявление переменных. Показательно.

Теперь запуск

$ ./node_modules/.bin/webpack

И открываем в браузере. В консоли должно быть что-то вроде

keyA valueA
keyB valueB

Я помоему действительно не расчитал количество букв и видимо придется дробить на две статьи. В следующей мы рассмотрим, что такое Vue.js что такое Vuex, как это все собрать вместе. А сейчас еще пару вещей

$ ./node_modules/.bin/webpack -wd

Запустит webpack в виде продолжительного процесса, который будет следить за любым изменением в любом файле, который входит в bundle и сразу собирать новую версию этого бандла. И это будет быстро! Дополнительно генерирует map файлы, чтобы быстро можно было ориентироваться в коде во время выполнения. Полезно для дебага.

$ ./node_modules/.bin/webpack -p

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

Иииии...

Домашнее задание

Или что еще почитать на тему.

Как я уже говорил, штудируем

Ну и если интересно, всегда можете написать мне сюда или на почту (ссылка слева).

Спасибо за внимание, извините за МНОГАБУКАВ