[Django]Grunt + LiveReload를 이용한 자동 테스크 환경 구축

Edit

[Django]Grunt + LiveReload를 이용한 자동 테스크 환경 구축

Grunt를 이용하면 프론트 단계에서 필요한 다양한 테스크들을 자동화 시킬 수 있다. Grunt 중에서 grunt-contrib-connect와 watch를 결합하면 css나 js 파일들의 변경 사항이 있을 경우, 화면을 자동으로 refresh 해준다. 하지만 grunt-contrib-connect는 express로 local server를 띄우는 것이기 때문에 Django의 localserver와 같이 사용하기 힘들다.

따라서 Grunt의 watch로 static 파일의 변경사항이 있을 경우, 자동으로 concat을 시키고 Django에서 concat를 watch를 시켜 static 파일들이 변동이 생겼을 때 자동으로 페이지를 reload 시켜주는 것을 해보고자 한다.

먼저 Grunt를 설정해보자.(기본 npm, grunt 설치는 생략) Gruntfile.js는 자신 입맛대로 변경하면 된다. 중요한 것은 Django가 watch 할 수 있는 static 파일만 제대로 만들어주면 된다.

$ npm install --save-dev grunt-contrib-sass grunt-contrib-concat grunt-contrib-cssmin grunt-contrib-watch grunt-contrib-uglify
// Gruntfile.js
module.exports = function (grunt) {
grunt.initConfig({
sass: {
dist: {
files: {
...
}
}
},

concat: {
js: {
src: [
'static/app/js/**/*.js'
],
dest: 'public/js/app.js'
},
vendor_js: {
src: [
'static/vendor/js/**/*.js'
],
dest: 'public/js/vendor.js'
},
css: {
src: [
'static/app/css/**/*.css'
],
dest: 'public/css/styles.css'
},
vendor_css: {
src: [
'static/vendor/css/**/*.css'
],
dest: 'public/css/vendor.css'
}
},

uglify: {
target: {
files: [{
expand: true,
cwd: 'public/js',
src: ['*.js', '!*.min.js'],
dest: 'public/js',
ext: '.min.js'
}]
}
},

cssmin: {
target: {
files: [{
expand: true,
cwd: 'public/css',
src: ['*.css', '!*.min.css'],
dest: 'public/css',
ext: '.min.css'
}]
}
},

watch: {
sass: {
files: 'static/app/sass/**/*.scss',
tasks: ['sass:dist', 'concat:css', 'cssmin:target'],
options: {
interrupt: true
}
},
js: {
files: 'static/app/js/**/*.js',
tasks: ['concat:js', 'uglify:target'],
options: {
interrupt: true
}
}
}
}
)
;

grunt.loadNpmTasks('grunt-contrib-sass');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-uglify');

grunt.registerTask('s', ['watch:sass']);
grunt.registerTask('w', ['watch:js']);

};

이제 Django가 static 파일을 watch 할 수 있도록 라이브러리를 설치하자. Livereloaddj-static를 사용할 것이다.

$ pip install dj-static
$ pip install livereload

이제 livereload를 INSTALLED_APPS에 추가해주자.

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

STATIC_ROOT = os.path.join(BASE_DIR, 'public')
STATIC_URL = '/static/'

그 다음 wsgi.py에 추가하자.

import os
from django.conf import settings
from django.core.wsgi import get_wsgi_application
from dj_static import Cling

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings")

if settings.DEBUG:
application = Cling(get_wsgi_application())
else:
application = get_wsgi_application()

이제 livereload를 시키자. live 시키기 전에 grunt watch 걸어주는 걸 잊지 말자.

$grunt w
$grunt s
$python manage.py livereload

localhost:8000에 접속하면 잘 적용 된 것을 볼 수 있다.

%23%5BDjango%5DGrunt%20+%20LiveReload%uB97C%20%uC774%uC6A9%uD55C%20%uC790%uB3D9%20%uD14C%uC2A4%uD06C%20%uD658%uACBD%20%uAD6C%uCD95%0A@%28Django%29%5Bdjango%7Cgrunt%7Clivereload%5D%0A%0A%60Grunt%60%uB97C%20%uC774%uC6A9%uD558%uBA74%20%uD504%uB860%uD2B8%20%uB2E8%uACC4%uC5D0%uC11C%20%uD544%uC694%uD55C%20%uB2E4%uC591%uD55C%20%uD14C%uC2A4%uD06C%uB4E4%uC744%20%uC790%uB3D9%uD654%20%uC2DC%uD0AC%20%uC218%20%uC788%uB2E4.%20%60Grunt%60%20%uC911%uC5D0%uC11C%20%60grunt-contrib-connect%60%uC640%20watch%uB97C%20%uACB0%uD569%uD558%uBA74%20css%uB098%20js%20%uD30C%uC77C%uB4E4%uC758%20%uBCC0%uACBD%20%uC0AC%uD56D%uC774%20%uC788%uC744%20%uACBD%uC6B0%2C%20%uD654%uBA74%uC744%20%uC790%uB3D9%uC73C%uB85C%20refresh%20%uD574%uC900%uB2E4.%20%uD558%uC9C0%uB9CC%20%60grunt-contrib-connect%60%uB294%20express%uB85C%20local%20server%uB97C%20%uB744%uC6B0%uB294%20%uAC83%uC774%uAE30%20%uB54C%uBB38%uC5D0%20Django%uC758%20localserver%uC640%20%uAC19%uC774%20%uC0AC%uC6A9%uD558%uAE30%20%uD798%uB4E4%uB2E4.%0A%0A%uB530%uB77C%uC11C%20%60Grunt%60%uC758%20watch%uB85C%20static%20%uD30C%uC77C%uC758%20%uBCC0%uACBD%uC0AC%uD56D%uC774%20%uC788%uC744%20%uACBD%uC6B0%2C%20%uC790%uB3D9%uC73C%uB85C%20%60concat%60%uC744%20%uC2DC%uD0A4%uACE0%20Django%uC5D0%uC11C%20%60concat%60%uB97C%20watch%uB97C%20%uC2DC%uCF1C%20static%20%uD30C%uC77C%uB4E4%uC774%20%uBCC0%uB3D9%uC774%20%uC0DD%uACBC%uC744%20%uB54C%20%uC790%uB3D9%uC73C%uB85C%20%uD398%uC774%uC9C0%uB97C%20reload%20%uC2DC%uCF1C%uC8FC%uB294%20%uAC83%uC744%20%uD574%uBCF4%uACE0%uC790%20%uD55C%uB2E4.%0A%0A%uBA3C%uC800%20%60Grunt%60%uB97C%20%uC124%uC815%uD574%uBCF4%uC790.%28%uAE30%uBCF8%20npm%2C%20grunt%20%uC124%uCE58%uB294%20%uC0DD%uB7B5%29%20%60Gruntfile.js%60%uB294%20%uC790%uC2E0%20%uC785%uB9DB%uB300%uB85C%20%uBCC0%uACBD%uD558%uBA74%20%uB41C%uB2E4.%20%uC911%uC694%uD55C%20%uAC83%uC740%20Django%uAC00%20watch%20%uD560%20%uC218%20%uC788%uB294%20static%20%uD30C%uC77C%uB9CC%20%uC81C%uB300%uB85C%20%uB9CC%uB4E4%uC5B4%uC8FC%uBA74%20%uB41C%uB2E4.%0A%0A%60%60%60bash%0A%24%20npm%20install%20--save-dev%20grunt-contrib-sass%20grunt-contrib-concat%20grunt-contrib-cssmin%20grunt-contrib-watch%20grunt-contrib-uglify%0A%60%60%60%0A%0A%60%60%60javascript%0A//%20Gruntfile.js%0Amodule.exports%20%3D%20function%20%28grunt%29%20%7B%0A%20%20%20%20grunt.initConfig%28%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20sass%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dist%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20files%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20...%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20concat%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20js%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20src%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27static/app/js/**/*.js%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dest%3A%20%27public/js/app.js%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20vendor_js%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20src%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27static/vendor/js/**/*.js%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dest%3A%20%27public/js/vendor.js%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20css%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20src%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27static/app/css/**/*.css%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dest%3A%20%27public/css/styles.css%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20vendor_css%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20src%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27static/vendor/css/**/*.css%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dest%3A%20%27public/css/vendor.css%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20uglify%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20target%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20files%3A%20%5B%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20expand%3A%20true%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cwd%3A%20%27public/js%27%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20src%3A%20%5B%27*.js%27%2C%20%27%21*.min.js%27%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dest%3A%20%27public/js%27%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ext%3A%20%27.min.js%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20cssmin%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20target%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20files%3A%20%5B%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20expand%3A%20true%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cwd%3A%20%27public/css%27%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20src%3A%20%5B%27*.css%27%2C%20%27%21*.min.css%27%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dest%3A%20%27public/css%27%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ext%3A%20%27.min.css%27%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20watch%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sass%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20files%3A%20%27static/app/sass/**/*.scss%27%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tasks%3A%20%5B%27sass%3Adist%27%2C%20%27concat%3Acss%27%2C%20%27cssmin%3Atarget%27%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20options%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20interrupt%3A%20true%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20js%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20files%3A%20%27static/app/js/**/*.js%27%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tasks%3A%20%5B%27concat%3Ajs%27%2C%20%27uglify%3Atarget%27%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20options%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20interrupt%3A%20true%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%29%0A%20%20%20%20%3B%0A%0A%20%20%20%20grunt.loadNpmTasks%28%27grunt-contrib-sass%27%29%3B%0A%20%20%20%20grunt.loadNpmTasks%28%27grunt-contrib-concat%27%29%3B%0A%20%20%20%20grunt.loadNpmTasks%28%27grunt-contrib-cssmin%27%29%3B%0A%20%20%20%20grunt.loadNpmTasks%28%27grunt-contrib-watch%27%29%3B%0A%20%20%20%20grunt.loadNpmTasks%28%27grunt-contrib-uglify%27%29%3B%0A%0A%20%20%20%20grunt.registerTask%28%27s%27%2C%20%5B%27watch%3Asass%27%5D%29%3B%0A%20%20%20%20grunt.registerTask%28%27w%27%2C%20%5B%27watch%3Ajs%27%5D%29%3B%0A%0A%7D%3B%0A%60%60%60%0A%0A%0A%0A%uC774%uC81C%20Django%uAC00%20static%20%uD30C%uC77C%uC744%20watch%20%uD560%20%uC218%20%uC788%uB3C4%uB85D%20%uB77C%uC774%uBE0C%uB7EC%uB9AC%uB97C%20%uC124%uCE58%uD558%uC790.%20%60Livereload%60%20%uC640%20%60dj-static%60%uB97C%20%uC0AC%uC6A9%uD560%20%uAC83%uC774%uB2E4.%0A%0A%60%60%60bash%0A%24%20pip%20install%20dj-static%0A%24%20pip%20install%20livereload%0A%60%60%60%0A%0A%uC774%uC81C%20%60livereload%60%uB97C%20INSTALLED_APPS%uC5D0%20%uCD94%uAC00%uD574%uC8FC%uC790.%0A%60%60%60python%0A%23%20settings.py%0AINSTALLED_APPS%20%3D%20%5B%0A%20%20%20%20...%0A%20%20%20%20%27livereload%27%2C%0A%20%20%20%20...%0A%5D%0A%0ASTATIC_ROOT%20%3D%20os.path.join%28BASE_DIR%2C%20%27public%27%29%0ASTATIC_URL%20%3D%20%27/static/%27%0A%60%60%60%0A%0A%uADF8%20%uB2E4%uC74C%20%60wsgi.py%60%uC5D0%20%uCD94%uAC00%uD558%uC790.%0A%60%60%60python%0Aimport%20os%0Afrom%20django.conf%20import%20settings%0Afrom%20django.core.wsgi%20import%20get_wsgi_application%0Afrom%20dj_static%20import%20Cling%0A%0Aos.environ.setdefault%28%22DJANGO_SETTINGS_MODULE%22%2C%20%22server.settings%22%29%0A%0Aif%20settings.DEBUG%3A%0A%20%20%20%20application%20%3D%20Cling%28get_wsgi_application%28%29%29%0Aelse%3A%0A%20%20%20%20application%20%3D%20get_wsgi_application%28%29%0A%60%60%60%0A%0A%uC774%uC81C%20%60livereload%60%uB97C%20%uC2DC%uD0A4%uC790.%20live%20%uC2DC%uD0A4%uAE30%20%uC804%uC5D0%20grunt%20watch%20%uAC78%uC5B4%uC8FC%uB294%20%uAC78%20%uC78A%uC9C0%20%uB9D0%uC790.%0A%0A%60%60%60bash%0A%24grunt%20w%0A%24grunt%20s%0A%60%60%60%0A%0A%60%60%60bash%0A%24python%20manage.py%20livereload%0A%60%60%60%0A%0A%60localhost%3A8000%60%uC5D0%20%uC811%uC18D%uD558%uBA74%20%uC798%20%uC801%uC6A9%20%uB41C%20%uAC83%uC744%20%uBCFC%20%uC218%20%uC788%uB2E4.