2021. 6. 8. 00:05ㆍDevelop
[수정 내역]
1. 이 게시글은 2021.06.09 23:44에 수정되었습니다.
이 게시글은 삽질보다 카테고리 선정에 가장 많은 시간을 할애한 것 같습니다.
Django니깐 python에 작성해야 하나..? 그래도 Quasar는 Vue.js 기반인데 Frontend니 JS 카테고리에 넣어야 하나...?
이 글을 읽기 전에 웹에서의 Render 방식에 대해서 이해할 필요성이 있습니다.
어쨌든 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로 설정하거나 주석 처리합니다.
또한 writeToDisk를 true로 설정하여야만 개발서버 실행 및 변화감지시 파일을 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
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 방식을 채택하고 있습니다. 때문에
상황에 따라서 api나 graphql을 사용하지 않고 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만 로드하면 모두 렌더링 되기 때문에 app만 render_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 화면이 로드되는 것을 볼 수 있습니다.