Believe you can

If you can dream it, you can do it.

WebフレームワークでWebpackを使ってみよう〜Django+Webpack+Vue.js編〜

Webpackの第2弾です
前回はLaravelでしたが今度はDjangoでWebpackを使ってみようと思います
作るものは前回同じVue.jsのツリーです

jp.vuejs.org

環境

以下の環境で動かしました

  • Python@3.6.2
  • Django@2.0.2
  • Webpack@3.11.0
  • Vue@2.5.13
  • webpack-bundle-tracker@0.2.1
  • css-loader@0.28.10
  • style-loader@0.20.2

仮想環境とDjangoのインストール

以下のコマンドで環境とDjangoのインストールを行います

python3 -m venv env
source env/bin/activate
pip install django
pip install django-webpack-loader

プロジェクトを作ります
起動時にワーニングが出てしまうのでmigrateもしておきます

django-admin startproject django_webpack_vue
python manage.py migrate

以下のコマンドで http://localhost:8000/Django初期画面が表示されるか確認します

python manage.py runserver

フロントエンド

フロントエンドのインストールを行っていきます

npm init -f
npm install --save-dev webpack webpack-bundle-tracker vue
npm install --save-dev style-loader css-loader

webpack.config.js を作ります
JavaScriptコンパイルcssのロードの設定になります

var path = require("path")
var webpack = require('webpack')
var BundleTracker = require('webpack-bundle-tracker')

module.exports = {
  context: __dirname,
  entry: './public/js/app.js',
  output: {
      path: path.resolve('./assets/bundles/'),
      filename: "[name]-[hash].js",
  },

  plugins: [
    new BundleTracker({filename: './webpack-stats.json'}),
  ],

  module : {
    rules : [
      {
        test: /\.css/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              url: false,
              sourceMap: true,
            },
          },
        ],
      },
    ]
  },

  resolve: {
    alias: {
      'vue': path.resolve('./node_modules/vue/dist/vue.js'),
    }
  },
}

サンプル画面

サンプル画面を組み込んでいきます
Laravelとあまり変わらないので細かい説明は省きます

public/css/app.css

body {
  font-family: Menlo, Consolas, monospace;
  color: #444;
}
.item {
  cursor: pointer;
}
.bold {
  font-weight: bold;
}
ul {
  padding-left: 1em;
  line-height: 1.5em;
  list-style-type: dot;
}

public/js/app.js

import '../css/app.css';

window.Vue = require('vue');

// demo data
var data = {
  name: 'My Tree',
  children: [
    { name: 'hello' },
    { name: 'wat' },
    {
      name: 'child folder',
      children: [
        {
          name: 'child folder',
          children: [
            { name: 'hello' },
            { name: 'wat' }
          ]
        },
        { name: 'hello' },
        { name: 'wat' },
        {
          name: 'child folder',
          children: [
            { name: 'hello' },
            { name: 'wat' }
          ]
        }
      ]
    }
  ]
}

// define the item component
Vue.component('item', {
  template: '#item-template',
  props: {
    model: Object
  },
  data: function () {
    return {
      open: false
    }
  },
  computed: {
    isFolder: function () {
      return this.model.children &&
        this.model.children.length
    }
  },
  methods: {
    toggle: function () {
      if (this.isFolder) {
        this.open = !this.open
      }
    },
    changeType: function () {
      if (!this.isFolder) {
        Vue.set(this.model, 'children', [])
        this.addChild()
        this.open = true
      }
    },
    addChild: function () {
      this.model.children.push({
        name: 'new stuff'
      })
    }
  }
})

// boot up the demo
var demo = new Vue({
  el: '#demo',
  data: {
    treeData: data
  }
})

templates/index.html

{% load render_bundle from webpack_loader %}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>django_webpack_vue</title>
  </head>

  <body>
    {% verbatim %}
    <!-- item template -->
    <script type="text/x-template" id="item-template">
      <li>
        <div
          :class="{bold: isFolder}"
          @click="toggle"
          @dblclick="changeType">
          {{ model.name }}
          <span v-if="isFolder">[{{ open ? '-' : '+' }}]</span>
        </div>
        <ul v-show="open" v-if="isFolder">
          <item
            class="item"
            v-for="(model, index) in model.children"
            :key="index"
            :model="model">
          </item>
          <li class="add" @click="addChild">+</li>
        </ul>
      </li>
    </script>

    <p>(You can double click on an item to turn it into a folder.)</p>

    <!-- the demo root element -->
    <ul id="demo">
      <item
        class="item"
        :model="treeData">
      </item>
    </ul>
    {% endverbatim %}

    {% render_bundle 'main' %}
  </body>
</html>

Vue.jsのバインドとテンプレートエンジンの置き換え方法がかぶってしまっているため {% verbatim %} {% endverbatim %} で挟み込んであげる必要があります また、 {% render_bundle 'main' %} でwebpackで作られた main.js を組み込むことができます

続いてDjango側です

django_webpack_vue/views.py

from django.shortcuts import render
from django.http.response import HttpResponse
 
def index(request):
    return render(request, 'index.html')

django_webpack_vue/urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url
from . import views

urlpatterns = [
    path('admin/', admin.site.urls),
    url(r'^', views.index),
]

コンパイル

JavaScriptファイルとcssファイルをコンパイルします

./node_modules/.bin/webpack --config webpack.config.js

ローカル開発サーバを起動してアクセスしてみてVue.jsサイトのサンプルと同じ物が表示されていれば完成です
今回作ったソースはGitHubにあげてあります

github.com

まとめ

LaravelもDjangoもWebpack用のモジュールが用意されているので簡単に組み込みことができました
ただ、Webpackを導入するメリットがあまり感じられないのはただ組み込んでいるからなんだろうなぁ...
他の言語フレームワークを試し終わったら基礎知識を上げていかないと