Как быстро написать веб-сайт или веб-приложение и не увязнуть в сборщиках

в 19:02, , рубрики: Axios, Bootstrap, flask, python, vue, Программирование, Разработка веб-сайтов

Это маленькое руководство описывает создание реактивного веб-приложения используя отрисовку на стороне сервера (Server-Side Rendering, SSR). Клиентская часть являет собой полноценное Vue-приложение, в моём случае используя шаблон MVVM. Серверное приложение работает на микрофреймворке Flask, который может предоставить конечные точки подключения (endpoint) и отдать готовую HTML страницу. HTML страницы (расположены в подкаталоге myapp/templates) рендерятся шаблонизатором Jinja (устанавливается в качестве зависимости Flask).

Внимание: быстро ещё не значит, что статья предназначена для новичков.

Используемые технологии и фреймворки:

Для API используем протокол JSON-RPC www.jsonrpc.org/specification. Протокол отличается простотой, удобочитаемостью и без лишних костылей работает как на серверной, так и на клиентской стороне.

Подготовка

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

pip install flask flask-jsonrpc

Создаём каталог проекта и подготавливаем структуру внутри. С рекомендуемой структурой приложения можно ознакомиться здесь https://habr.com/ru/post/421887/

mkdir -p myapp/{myapp/{static/{js,css},ns_api,templates},config,data}
cd myapp

Скачиваем нужные файлы JS и CSS фреймворков

wget -O myapp/static/js/jquery-3.3.1.slim.min.js https://code.jquery.com/jquery-3.3.1.slim.min.js
wget -O myapp/static/js/popper.min.js https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js
wget -O myapp/static/js/bootstrap.min.js https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js
wget -O myapp/static/css/bootstrap.min.css https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css
wget -O myapp/static/js/vue.min.js https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js
wget -O myapp/static/js/axios.min.js https://unpkg.com/axios/dist/axios.min.js

Здесь есть зависимость jquery, но только для работы Bootstrap

Минимальное Flask приложение

Файл run.py для ручного старта и тестирования

#!/usr/bin/env python3

from myapp import app as application

application.run(host='0.0.0.0', port=8000)

Файл config/default.py для настройки приложения

import os
import sys

# Конфигурация
DEBUG = True
SQLDEBUG = False

SESSION_COOKIE_NAME = 'myapp'
SESSION_TYPE = 'filesystem'

TITLE = 'Проект'

DIR_BASE = '/'.join(os.path.dirname(os.path.abspath(__file__)).split('/')[:-1])
DIR_DATA = DIR_BASE + '/data'
# Генерировать можно утилитой pwgen
# Пример:
# pwgen -sy 64
SECRET_KEY = '''0123456789'''

# Логирование
LOG_FILE = DIR_DATA + '/myapp.log'
LONG_LOG_FORMAT = '%(asctime)s - [%(name)s.%(levelname)s] [%(threadName)s, %(module)s.%(funcName)s@%(lineno)d] %(message)s'
LOG_FILE_SIZE = 128 # Размер файла лога в МБ
Файл config/__init__.py

CONFIG = 'config.default'

Файл myapp/__init__.py

import config
import logging

from flask import Flask
from logging.handlers import RotatingFileHandler

app = Flask(__name__)
app.config.from_object(config.CONFIG)

app.config.from_envvar('FLASKR_SETTINGS', silent=True)

# Логирование
handler = RotatingFileHandler(app.config['LOG_FILE'],
    maxBytes=app.config['LOG_FILE_SIZE']*1024*1024,
    backupCount=1)
handler.setLevel(logging.INFO)
formatter = logging.Formatter(app.config['LONG_LOG_FORMAT'])
handler.setFormatter(formatter)
app.logger.addHandler(handler)

# API
from . import ns_api

from . import views

Файл myapp/ns_api/__init__.py

from flask_jsonrpc import JSONRPC

from .. import app

jsonrpc = JSONRPC(app, '/api')

from . import logic

Файл myapp/views.py

from myapp import app
from flask import render_template

from . import forms, models


@app.route('/')
def index():
    pagedata = {}
    pagedata['title'] = app.config['TITLE']
    pagedata['data'] = {
        "A": True,
        "B": False,
        "result": False
    }
    body = render_template('index.html', pagedata=pagedata)
    return body

Файл myapp/ns_api/logic.py

import operator

from . import jsonrpc


@jsonrpc.method('logic.and(A=bool, B=bool)')
def logic_and(A, B):
    """
    Логическое И
    """
    return operator.and_(A, B)


@jsonrpc.method('logic.not(A=bool)')
def logic_not(A):
    """
    Логическое НЕ
    """
    return operator.not_(A)


@jsonrpc.method('logic.or(A=bool, B=bool)')
def logic_or(A, B):
    """
    Логическое ИЛИ
    """
    return operator.or_(A, B)


@jsonrpc.method('logic.xor(A=bool, B=bool)')
def logic_xor(A, B):
    """
    Логическое ИСКЛЮЧАЮЩЕЕ ИЛИ
    """
    return operator.xor(A, B)

Устанавливаем права на запуск

chmod +x run.py

Клиентская сторона пользовательского интерфейса (фронтенд, front-end)

Файл myapp/templates/header.html

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" type="text/css" href="/static/css/bootstrap.min.css" />
    <script src="/static/js/jquery-3.3.1.slim.min.js"></script>
    <script src="/static/js/popper.min.js"></script>
    <script src="/static/js/bootstrap.min.js"></script>
    <script src="/static/js/vue.min.js"></script>
    <script src="/static/js/axios.min.js"></script>
    <title>{{ pagedata['title'] }}</title>
</head>

Файл myapp/templates/skeleton.html

<!DOCTYPE html>
<html lang="ru">
    {% include 'header.html' %}
<body>
<section id="app">

<div class="container-fluid">
{% block content %}
{% endblock %}
</div>

</section>

{% block script %}
<script type="text/javascript">
var app = new Vue({
    el: '#app',
    data: {
    },
    methods: {
    }
})
</script>
{% endblock %}

</body>
</html>

Файл myapp/templates/index.html

{% extends "skeleton.html" %}
{% block content %}

<h1>Микросервисная архитектура</h1>

<a href="http://127.0.0.1:8000/api/browse">http://127.0.0.1:8000/api/browse</a>

<h2>API</h2>

<pre>curl -i -X POST 
   -H "Content-Type: application/json; indent=4" 
   -d '{
    "jsonrpc": "2.0",
    "method": "logic.and",
    "params": {
        "A": true,
        "B": true
    },
    "id": "1"
}' http://127.0.0.1:8000/api
</pre>

<h3>Логические</h3>

<ul>
    <li>logic.and(A, B)</li>
    <li>logic.not(A)</li>
    <li>logic.or(A, B)</li>
    <li>logic.xor(A, B)</li>
</ul>
<h3>API</h3>
<div class="btn-group">
<div class="btn btn-outline-success" v-if="A" v-on:click="changeA">Истина</div>
<div class="btn btn-outline-danger" v-else v-on:click="changeA">Ложь</div>
<div class="btn btn-outline-secondary disabled">И</div>
<div class="btn btn-outline-success" v-if="B" v-on:click="changeB">Истина</div>
<div class="btn btn-outline-danger" v-else v-on:click="changeB">Ложь</div>
<div class="btn btn-outline-secondary disabled">=</div>
<div class="btn btn-success disabled" v-if="result">Истина</div>
<div class="btn btn-danger disabled" v-else>Ложь</div>
</div>
{% endblock %}

{% block script %}
<script type="text/javascript">
var app = new Vue({
    el: '#app',
    data: {{ pagedata['data']|tojson|safe }},
    methods: {
        changeA: function() {
            var vm = this;
            vm.A = !vm.A;
            vm.update();
        },
        changeB: function() {
            var vm = this;
            vm.B = !vm.B;
            vm.update();
        },
        update: function() {
            var vm = this;
            axios.post(
                '/api',
                {
                    "jsonrpc": "2.0",
                    "method": 'logic.and',
                    "params": {
                        "A": vm.A,
                        "B": vm.B
                    },
                    "id": 1
                }
            ).then(
                function(response) {
                    if ('result' in response.data) {
                        vm.result = response.data['result'];
                    }
                }
            );
        }
    }
})
</script>
{% endblock %}

Автор: RemiZOffAlex

Источник

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