LHJ

I'm a FE developer.

3월 - gulpfile.babel.js - module

01 Mar 2020 » node_gulp

두번째 걸프세팅 - 모듈화

처음 세팅은 gulpfile.babel.js를 처음 모듈화해봤습니다.
하면서 많은 시행착오를 거쳤습니다.
Jade(Pug), ejs, sass와 javascript 등 전반적인 공부가 충분치 않다는걸 느꼈습니다.

    • import config from "../../config";
      import {src} from "gulp";
      import open from "gulp-open";
          
      export const BrowserOpen = () => {
          const options = {
              uri: `http://localhost:${config.port}`,
              app: config.browser //chrome, firefox, iexplore, opera, safari
          };
          return src(config.path.browser.dest)
              .pipe(open(options)); // local 서버가 아닌 파일 경로로 열려면 '<%file.path%>' 를 넣어주면된다.
      };
      
    • import del from "del";
      import config from "../../config";
          
      export const Clean = () => {
          return del(
              [config.path.del.dest]
          );
      };
      
    • import {dest, src} from "gulp";
      import config from "../../config";
      import jsConcat from "gulp-concat";
      import babel from "gulp-babel";
          
      export const ConcatJs = () => {
          return src(config.path.js.src)
              .pipe(jsConcat(config.path.js.filename))
              .pipe(babel())
              .pipe(dest(config.path.js.dest));
      };
      
    • import {dest, lastRun, src} from "gulp";
      import config from "../../config";
      import ejs from "gulp-ejs";
      import rename from "gulp-rename";
      import connect from "gulp-connect";
          
      export const EjsCompile = () => {
          return src(config.path.ejs.src, {since: lastRun(EjsCompile)})
              .pipe(ejs({
                  msg: "Hello Gulp!"
              }))
              .pipe(rename({
                  extname: ".html"
              }))
              .pipe(dest(config.path.ejs.dest))
              .pipe(connect.reload())
      };
      
      • 미완성 모듈입니다.
        import fs from "fs";
              
        export const fsReadDir = () => {
            let fileList;
            let fileList2 = [];
            fileList = fs.readdirSync('./src/ejs/', {
                encoding: 'utf8',
                withFileTypes: true
            });
            fileList.forEach(function (file, index) {
                if (!file.isDirectory()) {
                    fileList2.push(file.name)
                }
            });
            console.log(fileList);
            console.log(fileList[0].isDirectory());
            console.log(fileList[3].isDirectory());
            console.log(fileList2);
        };
        
    • import {dest, src, lastRun} from "gulp";
      import config from "../../config";
      import jade from "gulp-jade";
      import connect from "gulp-connect";
          
      export const JadeCompile = () => {
          return src(config.path.jade.src, {since: lastRun(JadeCompile)})
              .pipe(jade({
                  pretty: true
              }))
              .pipe(dest(config.path.jade.dest))
              .pipe(connect.reload())
      };
      
    • import {dest, src} from "gulp";
      import config from "../../config";
      import connect from "gulp-connect";
          
      export const Libs = () => {
          return src(config.path.js.libs)
              .pipe(dest(config.path.js.libsDest))
              .pipe(connect.reload())
      };
      
    • import {src} from "gulp";
      import config from "../../config";
      import esLint from "gulp-eslint";
          
      export const LintEs = () => {
          return src(config.path.js.src)
              .pipe(esLint())
              .pipe(esLint.format())
      };
      
    • import {dest, src} from "gulp";
      import config from "../../config";
      import sassLint from "gulp-sass-lint";
      import sass from "gulp-sass";
      import postcss from "gulp-postcss";
      import autoprefixer from "autoprefixer";
      import cleanCss from "gulp-clean-css";
      import connect from "gulp-connect";
          
      export const Sass = () => {
          return src(config.path.sass.src, {sourcemaps: true})
              .pipe(sassLint({
                  options: {
                      formatter: 'stylish'
                  }
              }))
              .pipe(sassLint.format())
              .pipe(sass({
                  sourceComments: false, // source 위치 주석
                  outputStyle: 'compact' // nested, expanded, compact, compressed
              }).on('error', sass.logError))
              .pipe(postcss([autoprefixer()]))
              .pipe(cleanCss({format: 'keep-breaks'}))
              .pipe(dest(config.path.sass.dest, {sourcemaps: true}))
              .pipe(connect.reload())
      };
      
    • import connect from "gulp-connect";
      import config from "../../config";
          
      export const Server = () => {
          return connect.server({
              root: config.path.browser.dest,
              port: config.port,
              livereload: config.livereload
          })
      };
      
    • import {dest, src} from "gulp";
      import config from "../../config";
      import imagemin from "gulp-imagemin";
      import spritesmith from "gulp.spritesmith";
      import buffer from "vinyl-buffer";
      import merge from "merge-stream";
          
      export const SpriteAndImgCompress = () => {
          const imgCompress = src(config.path.img.src)
              .pipe(imagemin([
                  imagemin.gifsicle({interlaced: true}),
                  imagemin.mozjpeg({quality: 80, progressive: true}),
                  imagemin.optipng({optimizationLevel: 5}),
                  imagemin.svgo({
                      plugins: [
                          {removeViewBox: true},
                          {cleanupIDs: false}
                      ]
                  })
              ]))
              .pipe(dest(config.path.img.dest));
          const spriteData = src(config.path.spImg.src)
              .pipe(spritesmith({
                  retinaSrcFilter: config.path.retinaImg.src,
                  imgName: config.path.spImg.filename,
                  retinaImgName: config.path.retinaImg.filename,
                  imgPath: config.path.spImg.imgPath,
                  retinaImgPath: config.path.retinaImg.retinaImgPath,
                  cssName: '_sprite.scss',
                  padding: 2
              }));
          const imgStream = spriteData.img
              .pipe(buffer())
              .pipe(imagemin())
              .pipe(dest(config.path.spImg.dest));
          const cssStream = spriteData.css
              .pipe(dest('src/sass/modules/'));
          return merge(imgCompress, imgStream, cssStream);
      };
      
    • import {dest, src} from "gulp";
      import config from "../../config";
      import jsUglify from "gulp-uglify";
      import rename from "gulp-rename";
      import connect from "gulp-connect";
          
      export const UglifyJs = () => {
          return src(config.path.js.dest + config.path.js.filename, {sourcemaps: true})
              .pipe(jsUglify())
              .pipe(rename({suffix: '.min'}))
              .pipe(dest(config.path.js.dest, {sourcemaps: true}))
              .pipe(connect.reload())
      };
      
      • 처음시도한 모듈화 + 자바스크립트 이해도 부족으로 인해 소스가 쓸데없이 길어졌다.
        "use strict";
              
        // 모듈 호출
        // gulp             : gulp 모듈
        // gulp-eslint      : javascript 문구 문법 검사
        // gulp-concat      : javascript 파일을 한 파일로 합쳐주는 모듈
        // gulp-uglify      : javascript 파일을 min 파일처럼 압축해주는 모듈
        // gulp-babel       : es6를 es5로 컴파일 해주는 모듈
        // gulp-rename      : 파일의 이름을 바꿔주는 모듈
        // gulp-jade        : HTML 템플릿 중 하나인 jade 템플릿을 사용할 수 있게 하는 모듈
        // gulp-ejs         : HTML 템플릿 중 하나인 ejs 템플릿을 사용할 수 있게 하는 모듈
        // gulp-sass        : sass를 사용할 수 있게 해주는 모듈
        // gulp-clean-css   : css 파일을 이쁘게 압축해주는 모듈
        // del              : 폴더(디렉터리)/파일 제거
        // gulp-connect     : local 서버와 연결하게 해주는 모듈
        // gulp-open        : 브라우저를 열게하는 모듈
        // gulp-sass-lint   : sass 문법검사 모듈
        // autoprefixer     : vend prefix를 자동으로 달아주는 모듈
        // gulp-postcss     : 위 autoprefixer와 같이 쓰는 모듈
        // gulp-imagemin    : image 파일 용량을 줄여주는 모듈
        // gulp.spritesmith : sprite 이미지와 CSS 자동 생성 모듈
        // vinyl-buffer     : gulp.spritesmith 모듈과 gulp-imagemin 모듈을 연결시켜주는 모듈
        // merge-stream     : stream을 merge시켜주는 모듈, 종료신호를 묶어서 보낼 수 있다.
              
        import {watch, series, parallel} from 'gulp';
        import {Clean} from './clean';
        import {SpriteAndImgCompress} from './spriteAndCompress';
        import {Sass} from './sass';
        import {LintEs} from './lintjs';
        import {ConcatJs} from './concatjs';
        import {UglifyJs} from './uglifyjs';
        import {Libs} from './libjs';
        import {JadeCompile} from './jade';
        import {EjsCompile} from './ejs';
        import {Server} from './server';
        import {BrowserOpen} from './browserOpen';
        import config from '../config.json';
        import {fsReadDir} from './fsReadDir';
              
        // 파일 목록 읽기 - 테스트
        export const TestReadDir = () => {
            return fsReadDir();
        };
              
        // 파일 삭제
        export const FileDelete = () => {
            return Clean();
        };
              
        // 이미지 용량 줄이기 및 sprite
        const SpriteCompress = () => {
            return SpriteAndImgCompress();
        };
              
              
        // Sass 컴파일
        // 주의할 점, Jade 템플릿과 어떻게 연동했냐에 따라서 watch 함수 내부를 수정해줄 필요가 있다.
        // Jade 템플릿에 style 태그에 인라인 형식으로 불러왔으면 watch 형식에서 Jade 탬플릿까지 수정을해줘야하는 함수를 호출해야한다.
        const SassCompile = () => {
            return Sass();
        };
              
        // JS 문법 검사
        const InspectionJs = () => {
            return LintEs();
        };
              
        // JS 병합
        const CompressJs = () => {
            return ConcatJs();
        };
              
        // JS 압축
        const PressJs = () => {
            return UglifyJs();
        };
              
        // JS libs 내보내기
        const Libsjs = () => {
            return Libs();
        };
              
        // Jade 컴파일
        const Jade = () => {
            return JadeCompile();
        };
              
        // ejs 컴파일
        const Ejs = () => {
            return EjsCompile();
        };
              
        // 웹 서버 업무 (LiveReload 사용)
        const ServerMake = () => {
            return Server();
        };
              
        // 브라우저 오픈 업무
        const PageOpen = () => {
            return BrowserOpen();
        };
              
        // 지속적 관찰(Watch) 업무 정의
        // Jade Template 내부 인라인 형식으로 들어가게 했을지도 모르므로 다음과 같이 모든 함수 뒤에 JadeCompile 함수를 호출하였다.
        const FileWatch = () => {
            watch([config.path.img.src, config.path.spImg.src], SpriteCompress);
            watch([config.path.jade.src, config.path.jade.parts], Jade);
            watch([config.path.ejs.src, config.path.ejs.parts], Ejs);
            watch(config.path.sass.src, series(SassCompile, Jade, Ejs));
            watch(config.path.js.src, series(InspectionJs, CompressJs, PressJs, Jade, Ejs));
            watch(config.path.js.libs, Libsjs, Jade, Ejs);
        };
              
        /*
         * You could even use `export as` to rename exported tasks
         */
        exports.build = series(FileDelete, series(Libsjs, SpriteCompress), parallel(SassCompile, series(InspectionJs, CompressJs, PressJs)), Jade, Ejs);
              
        /*
         * You could even use `export as` to rename exported tasks
         */
        exports.watch = parallel(ServerMake, PageOpen, FileWatch);
              
        /*
         * Export a default task
         */
        exports.default = series(FileDelete, series(Libsjs, SpriteCompress), parallel(SassCompile, series(InspectionJs, CompressJs, PressJs)), Jade, Ejs, parallel(ServerMake, PageOpen, FileWatch));
          
        
      • <html lang="ko">
        <head>
            <meta http-equiv="content-type" content="text/html; charset=UTF-8">
            <title><%= title %></title>
            <style> body {
                    font-size: 12pt;
                    color: #006666;
                }
                h1 {
                    font-size: 18pt;
                    background-color: #AAFFFF;
                }
                pre {
                    background-color: #EEEEEE;
                }
            </style>
        </head>
        <body>
        <%
            var title = 'ejs'
         %>
        <h1><%= title %></h1>
        <p>Welcome to <%= title %>, 오잉 되네? 아싸 문법정리만 하자, 사용법만 확실하게 익히고 lastRun도 추가하고</p>
        <button class="<%= title %>" type="submit">전송</button>
        <input type="text" placeholder="<%= title + '연습' %>">
              
        EJS에서 변수는 &lt;%= %&gt;로 감쌉니다. <br>
        내부에 변수를 사용할 수도 있습니다. <br>
        자바스크립트 코드는 &lt;% %&gt; 안에 적어줍니다.
        </body>
        </html>
        
      • <html lang="ko">
        <head>
            <meta http-equiv="content-type" content="text/html; charset=UTF-8">
            <title><%= title %></title>
            <style> body {
                    font-size: 12pt;
                    color: #006666;
                }
                h1 {
                    font-size: 18pt;
                    background-color: #AAFFFF;
                }
                pre {
                    background-color: #EEEEEE;
                }
            </style>
        </head>
        <body>
        <%
            var title = 'ejs'
         %>
        <h1><%= title %></h1>
        <p>Welcome to <%= title %>, 오잉 되네? 아싸 문법정리만 하자, 사용법만 확실하게 익히고 lastRun도 추가하고</p>
        <button class="<%= title %>" type="submit">전송</button>
        <input type="text" placeholder="<%= title + '연습' %>">
              
        EJS에서 변수는 &lt;%= %&gt;로 감쌉니다. <br>
        내부에 변수를 사용할 수도 있습니다. <br>
        자바스크립트 코드는 &lt;% %&gt; 안에 적어줍니다.
        </body>
        </html>
        
      • <html lang="ko">
        <head>
            <meta http-equiv="content-type" content="text/html; charset=UTF-8">
            <title><%= title %></title>
            <style> body {
                    font-size: 12pt;
                    color: #006666;
                }
                h1 {
                    font-size: 18pt;
                    background-color: #AAFFFF;
                }
                pre {
                    background-color: #EEEEEE;
                }
            </style>
        </head>
        <body>
        <%
            var title = 'ejs'
         %>
        <h1><%= title %></h1>
        <p>Welcome to <%= title %>, 오잉 되네? 아싸 문법정리만 하자, 사용법만 확실하게 익히고 lastRun도 추가하고</p>
        <button class="<%= title %>" type="submit">전송</button>
        <input type="text" placeholder="<%= title + '연습' %>">
              
        EJS에서 변수는 &lt;%= %&gt;로 감쌉니다. <br>
        내부에 변수를 사용할 수도 있습니다. <br>
        자바스크립트 코드는 &lt;% %&gt; 안에 적어줍니다.
        </body>
        </html>
        
      • <html lang="ko">
        <head>
            <meta http-equiv="content-type" content="text/html; charset=UTF-8">
            <title><%= title %></title>
            <style> body {
                    font-size: 12pt;
                    color: #006666;
                }
              
                h1 {
                    font-size: 18pt;
                    background-color: #AAFFFF;
                }
              
                pre {
                    background-color: #EEEEEE;
                }
            </style>
        </head>
        <body>
        <%
            var title = 'ejs'
         %>
        <h1><%= title %></h1>
        <p>Welcome to <%= title %>, 오잉 되네? 아싸 문법정리만 하자, 사용법만 확실하게 익히고 lastRun도 추가하고</p>
        <button class="<%= title %>" type="submit">전송</button>
        <input type="text" placeholder="<%= title + '연습' %>">
              
        EJS에서 변수는 &lt;%= %&gt;로 감쌉니다. <br>
        내부에 변수를 사용할 수도 있습니다. <br>
        자바스크립트 코드는 &lt;% %&gt; 안에 적어줍니다.4444
        </body>
        </html>
        
      • /sp/
        • doctype html
          block lang
              - var language = 'ko'
          html(lang=language === 'ko' ? 'ko-KR' : language === 'en' ? 'en' : '')
              include _head
          body.dark-theme
              include _navigation
              | 안녕?
          
        • //- 현재 페이지 경로
          -var curPage='/'
                  
          //- 믹스인 2Depth 정의
          mixin navi2D(naviContents2)
              nav: ul
                  each D1_item in naviContents2
                      li
                          +naviLink(D1_item)
                          if D1_item.child
                              ul
                                  each D2_item in D1_item.child
                                      li
                                          +naviLink(D2_item)
                  
          mixin naviLink(item)
              a(href=item.link)=item.content+curPage
          
      • extends parts/_docTemplate
              
        block lang
            - var language = 'ko-KR'
              
        block pageTitle
            | 안녕하세요1
        
      • /libs/
        • // Scss 한줄 주석
          /* Scss 여러줄 주석, CSS로 컴파일 될 때 해석됩니다. */
          %btn {
            padding: 10px;
            border: 1px solid #222;
          }
                    
          .btn-check {
            @extend %btn;
            background-color: #cecece;
          }
                    
          .btn-click {
            @extend %btn;
            background-color: $fg-color;
          }
          
        • // 테마 변수 : light | dark
          $theme: dark;
                    
          // 조건 처리
          @if $theme == light {
            $bg-color: #f34e7b;
          } @else if $theme == very-light {
            $bg-color: #f8c9d6;
          } @else {
            $bg-color: #61051f;
          }
                    
          // 테마 클래스 속성 및
          // 배경/전경 컬러 색상 설정
          body.#{$theme}-theme {
            background-color: $bg-color;
            color: invert($bg-color);
          }
                    
          // if(조건, 참일 경우 값, 거짓일 경우 값)
          // 가운데 정렬 유무 설정
          $center-pos: false;
          $is-center: if($center-pos, auto, null);
                    
          .container {
            @include margin(null, $is-center);
          }
          
        • // 페이지 폭
          $page-width: 960px;
          // 컬럼 개수
          $columns: 24;
          // 거터
          $gutter: 20px;
          // 컬럼 폭
          $col-width: (($page-width - $gutter) - ($gutter*($columns - 1))) / $columns;
                    
          %col {
            float: left;
            margin-right: $gutter;
          }
                    
          // 조건이 3보다 작거나 같으면 코드 반복 생성
          @for $i from 1 through $columns {
            .col-#{$i} {
              @extend %col;
              width: ($col-width * $i) + ($gutter * ($i - 1));
            }
          }
                    
          // ---------------------------------------------
          /* 아이콘(icon) 설정 */
          // ---------------------------------------------
          @each $item, $w, $h in (git, 24px, 24px), (nodejs, 32px, 32px), (gulpjs, 24px, 24px), (jade, 16px, 16px) {
            .icon-#{$item} {
              width: $w;
              height: $h;
              background-image: url("images/#{$item},png");
            }
          }
                    
          // ---------------------------------------------
          /* 제목(Heading) 설정 */
          // ---------------------------------------------
          @each $H in (h1:3rem, h2:2rem, h3:1.7rem, h4:1.5rem, h5:1.3rem, h6:1rem) {
            #{nth($H, 1)} {
              font-size: nth($H, 2);
            }
          }
          
        • @mixin reset-list {
            padding-left: 0;
            list-style: none;
          }
                    
          @mixin html5-block {
            section, article, nav, aside, header, footer {
              display: block;
            }
          }
                    
          @mixin position($type:relative, $t:null, $l:null, $b:null, $r:null) {
            position: $type;
            top: $t;
            left: $l;
            bottom: $b;
            right: $r;
          }
                    
          @mixin size($w:null, $h:null) {
            width: $w;
            height: $h;
          }
                    
          @mixin transition($args...) {
            transition: $args;
          }
                    
          @mixin transform($args...) {
            transform: $args;
          }
                    
          @mixin margin($tb:null, $lr:null) {
            margin: {
              top: $tb;
              bottom: $tb;
              left: $lr;
              right: $lr;
            }
          }
          
        • _sprite.scss
        • // 폰트
          $ghothic: "Nanum Gothic", Dotum, Sans-serif;
          $myungjo: "Nanum Myungju", Gulim, serif;
          // 색상
          $fg-color: #382f1f;
          $bg-color: #fefcf5;
          
      • *, *::before, *::after {
          box-sizing: border-box;
        }
                
        html, body {
          height: 100%;
        }
                
        nav {
          background-color: #222;
        }
                
        #page {
          width: 100%;
          margin: 0 auto;
          padding: 30px;
                
          .article {
            position: relative;
            margin: {
              right: 10px;
              left: 30px;
            }
          }
                
          .container {
            padding-left: 5%;
            padding-right: 5%;
          }
                
          a {
            text-decoration: none;
            color: #069aea;
                
            &:hover {
              padding-bottom: 0.1rem;
              color: #023855;
            }
                
            .rgba & {
              color: rgba(6, 154, 234, 0.8);
            }
          }
        }
        
      • @import "modules/_sprite";
                
        /* 필요한 거 1배수만 가져오기 */
        .arrow-r {
          @include sprite($arrow-r);
        }
                
        /* 필요한 것만 1, 2배수 가져오기 */
        .arrow-r {
          @include retina-sprite($arrow-r-group);
        }
                
        /* 1배수 이미지 모두 */
        @include sprites($spritesheet-sprites);
                
        /* 1, 2배수 이미지 모두 */
        @include retina-sprites($retina-groups);
        
      • @charset "utf-8";
        @import "modules/_variables";
        @import "modules/_button";
        @import "pages/_page1";
        @import "modules/_mixins";
        @import "modules/_condition";
        @import "modules/_loop";
                
        @include html5-block;
                
        nav ul, #gbn ul, #lnb ul {
          @include reset-list;
          font-family: $myungjo;
          color: $bg-color;
        }
                
        .login-box {
          @include position(absolute, 0, 10px);
          @include size(24rem, 10rem);
        }
        /* 주석 */
        .error {
          @include position($type: absolute, $r: 10px);
          @include size(320px);
          @include transition(width 0.2s, height 0.4s ease-out);
          @include transform(scale(1.5));
        }
        
    •   <html lang="ko">
        <head>
            <meta http-equiv="content-type" content="text/html; charset=UTF-8">
            <title></title>
        </head>
        <body>
            
            
            
        </body>
        </html>
      
  • {
      "presets": [
        "@babel/preset-env"
      ]
    }
    
  • {
        "env": {
            "browser": true,
            "es6": true,
            "node": true
        },
        "extends": "eslint:recommended",
        "globals": {
            "Atomics": "readonly",
            "SharedArrayBuffer": "readonly"
        },
        "parserOptions": {
            "ecmaVersion": 2018,
            "sourceType": "module"
        },
        "rules": {
        }
    }
    
  • .idea/
    node_modules/
    dist/
    package-lock.json
    
  • {
      "port": 90,
      "livereload": true,
      "browser": "chrome",
      "path": {
        "del": {
          "dest": "dist/"
        },
        "browser": {
          "dest": "dist/"
        },
        "jade": {
          "src": "src/jade/**/!(_)*.jade",
          "parts": "src/jade/parts/*.jade",
          "dest": "dist/jade/"
        },
        "ejs": {
          "src": "src/ejs/**/!(_)*.{ejs,html}",
          "parts": "src/ejs/parts/*.ejs",
          "dest": "dist/ejs/"
        },
        "sass": {
          "src": "src/sass/**/*.{scss, sass}",
          "compassSrc": "src/sass/",
          "dest": "dist/css/"
        },
        "js": {
          "libs": "src/js/libs/**/*.js",
          "libsDest": "dist/js/libs/",
          "src": "src/js/*.js",
          "dest": "dist/js/",
          "filename": "DOMlibrary.js"
        },
        "img": {
          "src": "src/img/*",
          "dest": "dist/img/"
        },
        "spImg": {
          "src": "src/img/sp/*.png",
          "dest": "dist/img/sp/",
          "filename": "sprite.png",
          "imgPath": "../img/sp/sprite.png"
        },
        "retinaImg": {
          "src": "src/img/sp/*@2x.png",
          "filename": "sprite@2x.png",
          "retinaImgPath": "../img/sp/sprite@2x.png"
        }
      }
    }
    
  • {
      "name": "sass-training",
      "version": "1.0.0",
      "description": "",
      "main": "gulpfile.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "@babel/core": "^7.8.4",
        "@babel/preset-env": "^7.8.4",
        "@babel/register": "^7.8.3",
        "autoprefixer": "^9.7.4",
        "babel-cli": "^6.26.0",
        "babel-preset-env": "^1.7.0",
        "babel-register": "^6.26.0",
        "del": "^5.1.0",
        "ejs": "^3.0.1",
        "eslint": "^6.8.0",
        "gulp": "^4.0.2",
        "gulp-babel": "^8.0.0",
        "gulp-clean-css": "^4.2.0",
        "gulp-concat": "^2.6.1",
        "gulp-connect": "^5.7.0",
        "gulp-ejs": "^5.0.0",
        "gulp-eslint": "^6.0.0",
        "gulp-imagemin": "^7.1.0",
        "gulp-jade": "^1.1.0",
        "gulp-open": "^3.0.1",
        "gulp-postcss": "^8.0.0",
        "gulp-rename": "^2.0.0",
        "gulp-sass": "^4.0.2",
        "gulp-sass-lint": "^1.4.0",
        "gulp-uglify": "^3.0.2",
        "gulp.spritesmith": "^6.11.0",
        "merge-stream": "^2.0.0",
        "node-sass": "^4.13.1",
        "vinyl-buffer": "^1.0.1"
      },
      "browserslist": [
        "last 2 version",
        "> 2%",
        "IE 9",
        "IE 8"
      ]
    }