Django + Quasar.js 연동하기

2021. 6. 8. 00:05Develop

728x90

[수정 내역]

1. 이 게시글은 2021.06.09 23:44에 수정되었습니다.

 

 

 

이 게시글은 삽질보다 카테고리 선정에 가장 많은 시간을 할애한 것 같습니다.

Django니깐 python에 작성해야 하나..? 그래도 Quasar는 Vue.js 기반인데 Frontend니 JS 카테고리에 넣어야 하나...?

 

 

이 글을 읽기 전에 웹에서의 Render 방식에 대해서 이해할 필요성이 있습니다.

 

Modern Web의 4가지 렌더링 방식에 대한 고찰

Modern Web Modern Web 은 현대적인 웹 또는 최신 웹앱 혹은 멀티플랫폼 반응형 웹이라는 의미로 사용되는데, 정확한 사전 의미는 없고 직역하면 그냥 현대적인 웹입니다. 2021년 오늘날에는 아마도 Rea

gmyankee.tistory.com

 

 

어쨌든 Django에서는 Quasar Framework를 연동하는 방법을 기술하고자 합니다.

Django는 django-webpack-loader라는 라이브러리와 클라이언트에서는 webpack bundle tracker를 이용해서

build 한 데이터들을 tracking 하여 static파일을 serving 할 수 있도록 도와주는 좋은 라이브러리를 가지고 있습니다.

이를 이용하여 vue.js를 연동하는 것은 정말 쉽습니다.

 

하지만 안타깝게도 quasar의 경우 1.15+ 현재 기준에서는 vue.config.js나 main.js가 존재하지 않아 entry설정 등이 없어서 상당히 어려움을 겪고 있는 유저들이나 검색해도 0.*+ 버전일 때 작성된 일본어 게시글 밖에 존재하지 않습니다... 

 

 

시작하기전 프로젝트 구조

.
└── project/
    ├── project/
    │   ├── settings.py
    │   ├── asgi.py
    │   ├── urls.py
    │   └── wsgi.py
    ├── app/
    │   ├── admin.py
    │   ├── apps.py
    │   ├── models.py
    │   ├── tests.py
    │   └── views.py
    ├── templates/
    ├── static
    └── frontend/
        ├── node_modules/
        ├── public/
        ├── src/
        ├── assets/
        ├── boot/
        ├── components/
        ├── css/
        ├── dist/
        ├── pages/
        ├── router/
        │   ├── index.ts
        │   └── routes.ts
        ├── App.vue
        ├── quasar.conf.js
        └── webpack-stats.json

 

 

 

 

 

Quasar 설정

 

quasar.conf.js 설정

npm install --save-dev webpack-bundle-tracker@0.4.0-beta

quasar에 webpack-bundle-tracker를 설치합니다. 마찬가지로 버그가 있었던 기억이 있어서 0.4.0으로 설치하였습니다.

 

// package.json
{
  "scripts": {
    "dev": "quasar dev",
    "build:dev": "set BUILD_MODE=true && quasar build",
    "build:product": "set NODE_ENV=production&& set BUILD_MODE=true&& quasar build"
  },
 }

밑에서 다룰 publicPath 와 webpack-stats 파일명 설정을 위해 BUILD_MODE를 설정합니다.

 

 

// frontend/quasar.conf.js
const { configure } = require('quasar/wrappers')
const BundleTracker = require('webpack-bundle-tracker')

const BUILD_MODE = process.env.BUILD_MODE
const PRODUCTION = process.env.NODE_ENV === 'production'
const PUBLIC_PATH =
  BUILD_MODE && PRODUCTION ? 'https://storage.googleapis.com/overmap-assets/static/' : '/static/'
const STATS_FILENAME = BUILD_MODE && PRODUCTION ? 'webpack-stats-prod.json' : 'webpack-stats.json'

module.exports = configure(function(/* ctx */) {
  return {
	// ... 생략
    // Full list of options: https://v1.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
    build: {
      vueRouterMode: 'history', // available values: 'hash', 'history'
      filename: '[name].[chunkhash].js',
      // ... 생략

      // https://v1.quasar.dev/quasar-cli/handling-webpack
      // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
      chainWebpack(chain, { isServer, isClient }) {
      	// ... 생략
        chain.output.publicPath(PUBLIC_PATH) // make chaining js public path
      },
      extendWebpack(/* cfg */ cfg) {
        // do something with Electron main process Webpack cfg
        // chainWebpack also available besides this extendWebpack
        cfg.plugins.push(
          new BundleTracker({
            filename: `./${STATS_FILENAME}`,
            publicPath: PUBLIC_PATH,
          })
        )
      },
      // ... 생략
    },

    // Full list of options: https://v1.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer
    devServer: {
      hot: true,
      https: false,
      port: 8080,
      // open: true, // opens browser window automatically
      compress: true, // gzip
      writeToDisk: true,
    },
  }
})

 

눈 여겨 봐야할 부분은 총 3가지 입니다.

chainWebpack에서 chain.output.publicPath 이걸 추가 안해주시면,

webpack-stats.json에서 추적하는 파일말고 다른 참조 js파일들을 publicPath 경로를 계속 root로 설정합니다.

만약 django에서 STATIC_URL 이 /staticfiles/quasar 라면 publicPath에 /staticfiles/quasar를 작성해주셔야 합니다.

 

 

두번째는 extendWebpack 입니다. 

extendWebpack에서는 webpack-bundle-tracker를 추가하여 django-webpack-loader가

Vue 파일이나 JS파일의 변화를 감지할 수 있도록 하여야 합니다.

 

 

마지막으로 devServer 입니다.

quasar의 경우 dev(개발)모드 실행시 webpack devServer가 동작하여 브라우저가 열리는데, 이 기능을 버립니다.

정확히는 webpack의 개발서버는 백그라운드에서 hot-reload만 작동하고 django 페이지에서 한번에 보기 위해 

open을 false로 설정하거나 주석 처리합니다.

 

또한 writeToDisktrue로 설정하여야만 개발서버 실행 및 변화감지시 파일을 disk에 생성합니다.

webpack 개발서버는 기본적으로 변화 파일을 저장하지 않고, 모든 변화 과정을 메모리에 상주하고 있기 때문에

이 과정을 안하면 django에서 static 파일을 추적하더라도 불러올 수 없습니다.

 

hot은 실시간으로 변화할 경우 변화를 강제로 reload 즉 새로고침을 하지 않고 변화된 화면을 보기 위함입니다.

 

 

 

 

 

 

마지막으로 quasar의 경우 quasar.conf.js에서 webpack설정을 할 때 publicPath가 routes에도 영향을 주는 것을 볼 수 있습니다. 이러한 현상을 개선하기 위해서는 router의 index.ts에서 base를 변경하여야 합니다.

 

// frontend/src/router/index.ts


import { route } from 'quasar/wrappers'
import VueRouter from 'vue-router'
import { Store } from 'vuex'
import { StateInterface } from '../store'
import routes from './routes'

/*
 * If not building with SSR mode, you can
 * directly export the Router instantiation
 */

export default route<Store<StateInterface>>(function({ Vue }) {
  Vue.use(VueRouter)

  return new VueRouter({
    scrollBehavior: (to, from, savedPosition) => {
      if (savedPosition) {
        return savedPosition
      } else {
        return { x: 0, y: 0 }
      }
    },
    routes,

    // Leave these as is and change from quasar.conf.js instead!
    // quasar.conf.js -> build -> vueRouterMode
    // quasar.conf.js -> build -> publicPath
    mode: process.env.VUE_ROUTER_MODE,
    // base: process.env.VUE_ROUTER_BASE,
    base: '/',
  })
})

base를 VUE_ROUTER_BASE가 아닌 그냥 '/'로 처리합니다.

이렇게 되면 vue router로 생성된 anchor 태그들은 /static/child가 아닌 /child로 판별하게 되어 문제가 사라집니다.

 

이제 Quasar 설정은 끝입니다.

 

 

 

 

 

Django 설정

 

Django Base 설정

https://github.com/django-webpack/django-webpack-loader

 

django-webpack/django-webpack-loader

Transparently use webpack with django. Contribute to django-webpack/django-webpack-loader development by creating an account on GitHub.

github.com

pip install django-webpack-loader==0.7.0

django에서는 webpack-loader를 설치합니다. 1.0.0에서는 오류가 발견되어 0.7.0으로 설치하는 것을 권장드립니다.

 

 

settings.py에서 WEBPACK_LOADER를 추가하고 설정합니다.

# settings.py
INSTALLED_APPS = [
	# ...
	'webpack_loader',
    # ...
]

BASE_DIR = Path(__file__).resolve().parent.parent
FRONTEND_DIR = BASE_DIR / 'frontend'


# 개발중일때
WEBPACK_LOADER = {
    'DEFAULT': {
        'CACHE': not DEBUG,
        'BUNDLE_DIR_NAME': 'dist/',  # must end with slash
        'STATS_FILE': os.path.join(FRONTEND_DIR, 'webpack-stats.json'),
        'POLL_INTERVAL': 0.1,
        'TIMEOUT': None,
        'IGNORE': [r'.+\.hot-update.js', r'.+\.map'],
        # 'LOADER_CLASS': 'webpack_loader.loader.WebpackLoader',
    }
}

# 프로덕션 배포시
WEBPACK_LOADER = {
    'DEFAULT': {
        'BUNDLE_DIR_NAME': 'staticfiles/',
        'STATS_FILE': os.path.join(FRONTEND_DIR, 'webpack-stats-prod.json')
    }
}

webpack-stats.json을 통해서 bundler tracker로 번들링 된 파일 추적을 진행합니다.

제가 써본 경험상 BUNDLE_DIR_NAME은 있으나 마나 한데, 그냥 써주었습니다... 안써도 잘 동작 합니다.

경로는 README.md 를 읽어보면 STATS_FILE의 위치를 기점으로 작성하는 것 같습니다.

 

 

Django View 작성

# views.py
from django.views.generic import TemplateView


class VueRender(TemplateView):
    template_name = 'index.html'

 

귀찮으니깐 TemplateView로 대충 만듭니다.

Youtube또한 Template JSON Rendering 방식을 채택하고 있습니다. 때문에

상황에 따라서 apigraphql을 사용하지 않고 django에서 rendering 하신다면 다른 View를 사용하셔도 무방합니다.

혹은 초기 데이터를 미리 채워줄 수 있으니 아주 개이득인 부분입니다.

 

 

 

Global Context Processor [선택사항]

이 과정은 필수는 아니니깐 생략하셔도 좋습니다.

저는 nuxt나 quasar에서 사용하는 title template을 고정적으로 사용하기 위해 이 부분을 작성해야만 했습니다.

어떤 페이지에서든 나타나는 템플릿 태그를 하려면 context_processors를 추가하여만 합니다.

 

# context_processors.py
def search_optimization(request):
    return {
        'title_template': '제목ㅋ__ㅋ'
    }

 

# app/settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'core.context_processors.search_optimization'
            ],
            # 'debug': DEBUG,
        },
    },
]

 

 

Django HTML 설정

{% load render_bundle from webpack_loader %}
<!DOCTYPE html>
<html xml:lang=ko lang="ko">
<head>
    <title>메인 페이지 - {{ title_template }}</title>
    {% include 'head.html' %}
</head>
<body>
<div id="q-app"></div>
{% render_bundle 'app' %}
</body>
</html>

quasar에서 build후 webpack-stats.json을 열어보면 저는 app, null, undefined 총 3가지의 항목이 존재합니다.

하지만 app.js만 로드하면 모두 렌더링 되기 때문에 apprender_bundle 하였습니다.

 

 

webpack.stats.json은 다음과 같습니다. chunks 하위 키들이 render_bundle의 entry name이 됩니다.

{
  "status": "done",
  "publicPath": "static/",
  "chunks": {
    "undefined": [
      {
        "name": "20.js",
        "publicPath": "static/20.js",
        "path": "C:\\Users\\Base\\PycharmProjects\\project\\frontend\\dist\\20.js"
      }
    ],
    "null": [
      {
        "name": "19.js",
        "publicPath": "static/19.js",
        "path": "C:\\Users\\Base\\PycharmProjects\\project\\frontend\\dist\\19.js"
      }
    ],
    "app": [
      {
        "name": "app.js",
        "publicPath": "static/app.js",
        "path": "C:\\Users\\Base\\PycharmProjects\\project\\frontend\\dist\\app.js"
      }
    ]
  }
}

 

위 와 같이 설정을 모두 진행하셨다면 Quasar가 django를 통해 serving 되는 모습을 볼 수 있습니다.

이제 django html 파일에 SEO와 대충 보여줄 모델 데이터를 뿌려놓으면 크롤 봇들이 수집된 데이터를 반영할 것입니다.

사용자가 접근할 경우엔 잠깐 django html을 먼저 로드 후 Quasar의 vue 화면이 로드되는 것을 볼 수 있습니다.

728x90