[T.Viewer 개발일기] 3. Vue & Vuetify, Webpack 나의 경험담 2019.10.28 23:29

Vue를 사용해 볼까 해서 좀 둘러봤는데 넘넘 쉬웠다. Vuetify, Webpack까지 적용한 경험을 간단하게 정리해본다.

Vue는 Vue cli로

Vue는 그냥 쓰면되는데, 막상 개발 환경을 구축하려면 귀찮아지는것이 사실이다. 웹 쪽에는 다양한 라이브러리와 프레임웍이 많아도 너무 많아서 선택이 괴로운 지경이다. 대충 골랐다가 합이 안맞아서 갈아 엎으면 그것도 넘 큰 고생이고, 진짜 초반에 개발 환경 구축하는 것이 쉽지가 않다.

그런 고민을 눈치채고 vue-cli 3를 만들었다고 한다. vue-cli 2에서 한단계 큰 진화가 있는것 같은데, 나는 3부터 편하게 고고!ㅎ

how it can help improve the development experience — having in mind what developers need: performance, ease of use, and minimum configuration.

복수개의 개발 툴을 사용할 때, 설정을 줄이고 모범 예제를 제공하는 것이 목표라고 한다. 아래 링크 참고. What You Need to Know about Vue CLI 3

Vuetify와 Webpack도 순식간에 셋업

vue-cli 3를 알게된 후 기존 작업을 다 쓸어버리고 새로운 폴더에 다시 생성하기로 했다. vuetify, electron, webpack, eslint 등의 설정을 아주 간편하게 할 수 있을 뿐만 아니라 보기에도 좋다.

물론 롤모델로 삼고 있는 깃헙 데스크탑 앱처럼 언젠가 최적화가 필요할 수도 있겠지만, 최적화도 고려된 설계라고 홍보하고 있으니 한번 믿어 볼란다.

우선 vue-cli를 설치한다.

npm install -g @vue/cli
# OR
yarn global add @vue/cli
# 프로젝트 생성
vue create tviewer
cd tviewer

# electron-builder 플러그인 설치
vue add electron-builder

# vuetify 플러그인 설치
vue add vuetify

# eslint 플러그인 설치
vue add eslint

프로젝트를 생성하고, 플러그인들을 설치하고 실행하면 끝! 위에서 언급한 것처럼 package.json 설정 자체가 매우 깔끔하다. 물론 어디엔가 숨겨져있는 것이겠지만, default 값으로도 시작에 아무런 걸림돌이 되지 않는 것이 심리적 안정감을 선사해 주었다. 까다로운 webpack 설정도 별도 설정할 필요가 없으며, vue-cli-service 가 webpack 4를 사용해서 build 와 serve 등의 기능을 모두 처리해준다!

내가 변경한 것은 window 패키지를 인스톨러가 아닌 portable 형태로 나오도록 vue.config.js에 아래와 같이 추가한 것이 전부다.ㅎ

module.exports = {
  transpileDependencies: [
    'vuetify',
  ],
  pluginOptions: {
    electronBuilder: {
      builderOptions: {
        appId: 't.viewer',
        asar: false,
        productName: 'T.Veiwer',
        win: {
          target: [
            {
              target: 'portable',
              arch: [
                'x64',
              ],
            },
          ],
        },
      },
    },
  },
};

깃헙 데스크탑과 tviewer의 package.json은 엄청난 차이가 있다. 차이점을 확인해보고 검토해 보는 것만으로도 어찌할 줄 몰라 망설이는 상황에서 어느 정도 벗어 날 수 있다.

//깃헙 package.json
{
  "repository": {
    "type": "git",
    "url": "https://github.com/desktop/desktop.git"
  },
  "description": "GitHub Desktop build dependencies",
  "scripts": {
    "cli": "ts-node --require ./app/test/globals.ts --require ./app/src/cli/dev-commands-global.ts app/src/cli/main.ts",
    "test:integration": "cross-env TEST_ENV=1 ELECTRON_NO_ATTACH_CONSOLE=1 xvfb-maybe --auto-servernum -- jest --config ./app/jest.integration.config.js",
    "test:unit": "cross-env ELECTRON_RUN_AS_NODE=1 ./node_modules/.bin/electron ./node_modules/jest/bin/jest --detectOpenHandles --silent --config ./app/jest.unit.config.js",
    "test:unit:cov": "yarn test:unit --coverage",
    "test:script": "jest --silent --config ./script/jest.config.js",
    "test:script:cov": "yarn test:script --coverage",
    "test": "yarn test:unit:cov --runInBand && yarn test:script:cov && yarn test:integration",
    "test:setup": "ts-node -P script/tsconfig.json script/test-setup.ts",
    "test:review": "ts-node  -P script/tsconfig.json script/test-review.ts",
    "test:report": "codecov --disable=gcov -f app/coverage/coverage-final.json",
    "postinstall": "ts-node -P script/tsconfig.json script/post-install.ts",
    "start": "cross-env NODE_ENV=development ts-node -P script/tsconfig.json script/start.ts",
    "start:prod": "cross-env NODE_ENV=production ts-node -P script/tsconfig.json script/start.ts",
    "compile:dev": "cross-env NODE_ENV=development parallel-webpack --config app/webpack.development.ts",
    "compile:prod": "cross-env NODE_ENV=production NODE_OPTIONS='--max_old_space_size=4096' parallel-webpack --config app/webpack.production.ts",
    "build:dev": "yarn compile:dev && cross-env NODE_ENV=development ts-node -P script/tsconfig.json script/build.ts",
    "build:prod": "yarn compile:prod && cross-env NODE_ENV=production ts-node -P script/tsconfig.json script/build.ts",
    "package": "ts-node -P script/tsconfig.json script/package.ts",
    "generate-octicons": "ts-node -P script/tsconfig.json script/generate-octicons.ts",
    "clean:tslint": "rimraf tslint-rules/*.js",
    "compile:tslint": "tsc -P tslint-rules",
    "compile:script": "tsc -P script/tsconfig.json",
    "lint": "yarn prettier && yarn lint:src",
    "lint:fix": "yarn prettier --write && yarn lint:src:fix",
    "prettier": "prettier --check \"./**/*.{ts,tsx,js,json,jsx,scss,html,yaml,yml}\"",
    "lint:src": "yarn tslint && yarn eslint-check && yarn eslint",
    "lint:src:fix": "yarn tslint --fix && yarn eslint --fix",
    "tslint": "tslint -p .",
    "eslint": "eslint --cache --rulesdir ./eslint-rules \"./eslint-rules/**/*.js\" \"./{script,tslint-rules}/**/*.ts{,x}\" \"./app/{src,typings,test}/**/*.{j,t}s{,x}\" \"./changelog.json\"",
    "eslint-check": "eslint --print-config .eslintrc.* | eslint-config-prettier-check",
    "publish": "ts-node -P script/tsconfig.json script/publish.ts",
    "clean-slate": "rimraf out node_modules app/node_modules coverage && yarn",
    "rebuild-hard:dev": "yarn clean-slate && yarn build:dev",
    "rebuild-hard:prod": "yarn clean-slate && yarn build:prod",
    "changelog": "ts-node script/changelog/index.ts",
    "draft-release": "ts-node script/draft-release/index.ts",
    "validate-changelog": "ts-node script/validate-changelog.ts",
    "check-modified": "stop-build"
  },
  "author": {
    "name": "GitHub, Inc.",
    "email": "opensource+desktop@github.com",
    "url": "https://desktop.github.com/"
  },
  "license": "MIT",
  "engines": {
    "node": ">= 10",
    "yarn": ">= 1.9"
  },
  "dependencies": {
    "@typescript-eslint/eslint-plugin": "1.10.2",
    "@typescript-eslint/parser": "1.10.2",
    "airbnb-browser-shims": "^3.0.0",
    "ajv": "^6.4.0",
    "awesome-node-loader": "^1.1.0",
    "awesome-typescript-loader": "^5.2.1",
    "aws-sdk": "^2.23.0",
    "babel-core": "^6.26.3",
    "babel-jest": "^23.4.2",
    "babel-minify-webpack-plugin": "^0.3.1",
    "babel-plugin-syntax-dynamic-import": "^6.18.0",
    "chalk": "^2.2.0",
    "clean-webpack-plugin": "^0.1.19",
    "codecov": "^3.1.0",
    "cross-env": "^5.0.5",
    "css-loader": "^2.1.0",
    "eslint": "^5.16.0",
    "eslint-config-prettier": "^4.3.0",
    "eslint-plugin-babel": "^5.3.0",
    "eslint-plugin-json": "^1.4.0",
    "eslint-plugin-prettier": "^3.1.0",
    "eslint-plugin-react": "^7.13.0",
    "express": "^4.15.0",
    "fake-indexeddb": "^2.0.4",
    "file-loader": "^2.0.0",
    "front-matter": "^2.3.0",
    "fs-extra": "^6.0.0",
    "glob": "^7.1.2",
    "html-webpack-plugin": "^3.2.0",
    "jest": "^23.5.0",
    "jest-extended": "^0.11.2",
    "jest-junit": "^5.0.0",
    "jest-localstorage-mock": "^2.3.0",
    "json-pretty": "^0.0.1",
    "klaw-sync": "^3.0.0",
    "legal-eagle": "0.16.0",
    "mini-css-extract-plugin": "^0.4.0",
    "node-sass": "^4.12.0",
    "@primer/octicons": "^9.1.0",
    "parallel-webpack": "^2.3.0",
    "prettier": "1.16.0",
    "request": "^2.72.0",
    "rimraf": "^2.5.2",
    "sass-loader": "^7.0.1",
    "semver": "^5.5.0",
    "spectron": "5.0.0",
    "stop-build": "^1.1.0",
    "style-loader": "^0.21.0",
    "to-camel-case": "^1.0.0",
    "ts-jest": "^23.1.4",
    "ts-node": "^7.0.0",
    "tslint": "^5.11.0",
    "tslint-config-prettier": "^1.14.0",
    "tslint-microsoft-contrib": "^5.2.1",
    "tslint-react": "^3.6.0",
    "typescript": "^3.4.3",
    "typescript-tslint-plugin": "^0.0.6",
    "webpack": "^4.8.3",
    "webpack-bundle-analyzer": "^3.3.2",
    "webpack-dev-middleware": "^3.1.3",
    "webpack-hot-middleware": "^2.22.2",
    "webpack-merge": "^4.1.2",
    "xml2js": "^0.4.16",
    "xvfb-maybe": "^0.2.1"
  },
  "devDependencies": {
    "@types/byline": "^4.2.31",
    "@types/classnames": "^2.2.2",
    "@types/clean-webpack-plugin": "^0.1.2",
    "@types/codemirror": "0.0.55",
    "@types/double-ended-queue": "^2.1.0",
    "@types/electron-packager": "^13.0.0",
    "@types/electron-winstaller": "^2.6.0",
    "@types/event-kit": "^1.2.28",
    "@types/express": "^4.11.0",
    "@types/extract-text-webpack-plugin": "^3.0.3",
    "@types/file-url": "^2.0.0",
    "@types/fs-extra": "^5.0.2",
    "@types/fuzzaldrin-plus": "^0.0.1",
    "@types/glob": "^5.0.35",
    "@types/html-webpack-plugin": "^2.30.3",
    "@types/jest": "^23.3.1",
    "@types/keytar": "^4.0.0",
    "@types/legal-eagle": "^0.15.0",
    "@types/memoize-one": "^3.1.1",
    "@types/mini-css-extract-plugin": "^0.2.0",
    "@types/mri": "^1.1.0",
    "@types/node": "10.12.18",
    "@types/react": "^16.3.16",
    "@types/react-css-transition-replace": "^2.1.3",
    "@types/react-dom": "^16.0.5",
    "@types/react-transition-group": "1.1.1",
    "@types/react-virtualized": "^9.7.12",
    "@types/request": "^2.0.9",
    "@types/semver": "^5.5.0",
    "@types/strip-ansi": "^3.0.0",
    "@types/temp": "^0.8.29",
    "@types/textarea-caret": "^3.0.0",
    "@types/to-camel-case": "^1.0.0",
    "@types/ua-parser-js": "^0.7.30",
    "@types/untildify": "^3.0.0",
    "@types/uuid": "^3.4.0",
    "@types/webdriverio": "^4.13.0",
    "@types/webpack": "^4.4.0",
    "@types/webpack-bundle-analyzer": "^2.9.2",
    "@types/webpack-dev-middleware": "^2.0.1",
    "@types/webpack-hot-middleware": "^2.16.3",
    "@types/webpack-merge": "^4.1.3",
    "@types/winston": "^2.2.0",
    "@types/xml2js": "^0.4.0",
    "electron": "5.0.6",
    "electron-builder": "20.28.4",
    "electron-packager": "^13.1.0",
    "electron-winstaller": "2.5.2"
  }
}
//tviewer 의 그것
{
  "name": "tviewer",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "electron:build": "vue-cli-service electron:build",
    "electron:serve": "vue-cli-service electron:serve",
    "postinstall": "electron-builder install-app-deps",
    "postuninstall": "electron-builder install-app-deps"
  },
  "main": "background.js",
  "dependencies": {
    "core-js": "^3.1.2",
    "vue": "^2.6.10",
    "vuetify": "^2.1.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^4.0.0",
    "@vue/cli-plugin-eslint": "^4.0.4",
    "@vue/cli-service": "^4.0.0",
    "babel-eslint": "^10.0.1",
    "electron": "^6.0.0",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.0.0",
    "sass": "^1.19.0",
    "sass-loader": "^8.0.0",
    "vue-cli-plugin-electron-builder": "^1.4.0",
    "vue-cli-plugin-vuetify": "^1.1.1",
    "vue-template-compiler": "^2.6.10",
    "vuetify-loader": "^1.3.0"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "rules": {},
    "parserOptions": {
      "parser": "babel-eslint"
    }
  },
  "postcss": {
    "plugins": {
      "autoprefixer": {}
    }
  },
  "browserslist": [
    "> 1%",
    "last 2 versions"
  ]
}

마치며

점점 vue와 vuetify에 빠져드는 것 같다. 너무 쉽고, 개발자에 대한 배려가 많아서 참 쓰기 좋다. 자꾸만 손이 간다. 하지만 세상에 꽁자는 없다는 것을 잊지말자. 달콤함 뒤에 쓰라린 실패가 올 수도 있다. 눈 앞의 달콤함을 즐기면서도 돌 다리도 두둘기는 마음으로 하나씩, 한번씩 더 체크해보면서 흡수하자.