vue-cli3 project from scratch to docker deployment

Keywords: Front-end Vue axios Nginx Docker

1. Create a vue project

1.1 Install @vue/cli

# Global installation of vue-cli scaffolding
npm install -g @vue/cli
 Copy code



Wait until the installation is complete to start the next step

1.2 Initialization Project

vue create vue-cli3-project



  1. Choose a Presupposition

You can choose the default default, which includes babel,eslint

We choose more features for Manual select features

Enter later to select plug-in

  1. Plugin selection

Here we choose (Babel, Router, Vuex, Css preprocessor, Linter / Formatter format checking, Unit test framework)

  1. Routing mode selection

Whether to use history mode routing (Yes)

  1. Select a css preprocessor (Sass/SCSS)

  2. Select an eslint configuration

Select ESLint + Standard config here. Personally, I prefer this code specification.

  1. Choose when to do eslink validation

Lint on save is checked

If you are using the vscode editor, you can configure the eslink plug-in to automatically format the code

7. Choose Test Framework (Mocha + Chai)

8. Choose where to write these configuration files (In dedicated config files)

  1. Do you want to save this default configuration? (y)

If you choose, next time you create a vue project, you can use the default file directly without having to configure it again.

Waiting for Dependency Completion

2. Automatic registration of global components

Create a global directory under the components directory, where you place some components that need to be globally registered.

The function of index.js is to export component objects by introducing main.vue

Create an index.js in components to scan global objects and register automatically.

// components/index.js
import Vue from 'vue'

// Automatically load files at the end of.js in the global directory
const componentsContext = require.context('./global', true, /\.js$/)

componentsContext.keys().forEach(component => {
  const componentConfig = componentsContext(component)
  /**
  * Compatible with import export and require module.export
  */
  const ctrl = componentConfig.default || componentConfig
  Vue.component(ctrl.name, ctrl)
})




Finally, you can import this index.js into the main.js entry file.

3. Routing automatic introduction

Using routing in Vue project, I believe people who want to know are already familiar with how to use it. To add a new page, you need to configure the information of the page in the routing configuration.

If there are more and more pages, how can we make our routing more concise?

3.1 Split Routing

Split routing according to different business modules

Export a routing configuration array in each sub-module

Import all sub-modules in root index.js

3.2 Automatic Scanning Submodule Routing and Importing

As our business becomes larger and larger, every time we add new business modules, we need to add a sub-routing module under the routing, and then import it into index.js.

So how to simplify this operation?

Through the above automatic scanning global component registration, we can also achieve automatic scanning sub-module routing and import.

5. axios packaging

  • Install axios
npm install axios --save
// or
yarn add axios
//Copy code


5.1 Configure different environments

Create three new environment variable files in the root directory

Enter different addresses, such as dev's api address and test's api address.

# // .env
NODE_ENV = "development"
BASE_URL = "https://easy-mock.com/mock/5c4c50b9888ef15de01bec2c/api"
//Copy code


Then create a new vue.config.js in the root directory

// vue.config.js
module.exports = {
  chainWebpack: config => {
    // Here is the configuration of the environment, where different environments correspond to different BASE_URL s, so that the request addresses of axios are different.
    config.plugin('define').tap(args => {
      args[0]['process.env'].BASE_URL = JSON.stringify(process.env.BASE_URL)
      return args
    })
  }
}
//Copy code


Then create an api folder under the src directory and create an index.js to configure the configuration information for axios

// src/api/index.js
import axios from 'axios'
import router from '../router'
import { Message } from 'element-ui'
const service = axios.create({
  // Setting timeout time
  timeout: 60000,
  baseURL: process.env.BASE_URL
})
// When post ing requests, we need to add a request header, so we can make a default setup here.
// That is, set the request header of post to application/x-www-form-urlencoded;charset=UTF-8
service.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8''
export default service
//Copy code


5.2 Request Response Encapsulation

import axios from 'axios'
import router from '../router'
import { Message } from 'element-ui'
const service = axios.create({
  // Setting timeout time
  timeout: 60000,
  baseURL: process.env.BASE_URL
})

/**
 * Pre-request interception
 * Used to handle operations that need to be done before requests
 */
service.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if (token) {
    config.headers['Authorization'] = token
  }
  return config
}, (error) => {
  return Promise.reject(error)
})
/**
 * Request response interception
 * Used to handle operations that need to be returned after the request
 */
service.interceptors.response.use(response => {
  const responseCode = response.status
  // If the return status code is 200, the interface request is successful and the data can be obtained normally.
  // Otherwise, throw a mistake.
  if (responseCode === 200) {
    return Promise.resolve(response)
  } else {
    return Promise.reject(response)
  }
}, error => {
  // If the server returns a situation that does not start at 2, it will enter the callback.
  // Different operations can be performed according to the status code returned from the back end
  const responseCode = error.response.status
  switch (responseCode) {
    // 401: Not logged in
    case 401:
      // Jump to the login page
      router.replace({
        path: '/login',
        query: {
          redirect: router.currentRoute.fullPath
        }
      })
      break
    // 403: token expires
    case 403:
      // Pop-up error message
      Message({
        type: 'error',
        message: 'Logon information expires, please log in again'
      })
      // Clear token
      localStorage.removeItem('token')
      // Jump to the login page and pass the page you want to browse to FulPath. After successful login, jump to the page you need to visit.
      setTimeout(() => {
        router.replace({
          path: '/login',
          query: {
            redirect: router.currentRoute.fullPath
          }
        })
      }, 1000)
      break
    // 404 request does not exist
    case 404:
      Message({
        message: 'Network request does not exist',
        type: 'error'
      })
      break
    // Other errors, throw the error prompt directly
    default:
      Message({
        message: error.response.data.message,
        type: 'error'
      })
  }
  return Promise.reject(error)
})

export default service
//Copy code


Message method is a message prompt component provided by element-ui. Everyone can replace it according to their own message prompt component.

5.3 Disruption Processing

Adding processing logic to response interception

service.interceptors.response.use(response => {
  const responseCode = response.status
  // If the return status code is 200, the interface request is successful and the data can be obtained normally.
  // Otherwise, throw a mistake.
  if (responseCode === 200) {
    return Promise.resolve(response.data)
  } else {
    return Promise.reject(response)
  }
}, error => {
  // Outage or request timeout status
  if (!error.response) {
    // Request timeout status
    if (error.message.includes('timeout')) {
      console.log('Overtime')
      Message.error('Request timeout, please check whether the network is connected properly')
    } else {
      // Can display disconnection components
      console.log('Break the net.')
      Message.error('The request failed. Please check if the network is connected.')
    }
    return
  }
  // Eliminate other code······
  return Promise.reject(error)
})
//Copy code


5.4 Packaged Picture Upload

// src/api/index.js
export const uploadFile = formData => {
  const res = service.request({
    method: 'post',
    url: '/upload',
    data: formData,
    headers: { 'Content-Type': 'multipart/form-data' }
  })
  return res
}
//Copy code


call

async uploadFile (e) {
  const file = document.getElementById('file').files[0]
  const formdata = new FormData()
  formdata.append('file', file)
  await uploadFile(formdata)
}
//Copy code


5.5 Request Display Loading Effect

let loading = null
service.interceptors.request.use(config => {
  // Show the load box before the request
  loading = Loading.service({
    text: 'Loading in progress......'
  })
  // Eliminate other code······
  return config
}, (error) => {
  return Promise.reject(error)
})
service.interceptors.response.use(response => {
  // Close the load box after the request responds
  if (loading) {
    loading.close()
  }
 // Eliminate other code······
}, error => {
  // Close the load box after the request responds
  if (loading) {
    loading.close()
  }
  // Eliminate other code······    
  return Promise.reject(error)
})
//Copy code


6. Skillfully Use Mixins

6.1 Packaging store Common Method

Suppose there is a scenario where we encapsulate the function of getting a news list through vuex

import Vue from 'vue'
import Vuex from 'vuex'
import { getNewsList } from '../api/news'
Vue.use(Vuex)
const types = {
  NEWS_LIST: 'NEWS_LIST'
}
export default new Vuex.Store({
  state: {
    [types.NEWS_LIST]: []
  },
  mutations: {
    [types.NEWS_LIST]: (state, res) => {
      state[types.NEWS_LIST] = res
    }
  },
  actions: {
    [types.NEWS_LIST]: async ({ commit }, params) => {
      const res = await getNewsList(params)
      return commit(types.NEWS_LIST, res)
    }
  },
  getters: {
    getNewsResponse (state) {
      return state[types.NEWS_LIST]
    }
  }
})
//Copy code


Then on the news list page, we call Action and getters through mapAction, mapGetters, and we need to write these codes.

import { mapActions, mapGetters } from 'vuex'

computed: {
    ...mapGetters(['getNewsResponse'])
},
methods: {
    ...mapActions(['NEWS_LIST'])
}
//Copy code


Assuming that the interface to get the news list needs to be re-invoked on another page, we have to write the above code again, right?

Do you copy and paste wood?

If the interface suddenly adds a parameter, does not every code that uses the interface have to add this parameter?

Copy and paste for a while, and demand for a change, you will be cool.

Since it's duplicated code, we definitely need to reuse it, and the Mixin provided by Vue plays a big role.

  • Encapsulate news-mixin.js to create a mixins directory under src to manage all mixins and create a new news-mixin.js
import { mapActions, mapGetters } from 'vuex'
export default {
  computed: {
    ...mapGetters(['getNewsResponse'])
  },
  methods: {
    ...mapActions(['NEWS_LIST'])
  }
}
//Copy code


Then you can call the method directly by introducing the mixin into the components you need to use. No matter how many pages, just introduce this mixin and you can use it directly.

If the requirement is changed, you only need to modify the mixin file.

// news/index.vue
import Vue from 'vue'
import newsMixin from '@/mixins/news-mixin'
export default {
  name: 'news',
  mixins: [newsMixin],
  data () {
    return {}
  },
  async created () {
    await this.NEWS_LIST()
    console.log(this.getNewsResponse)
  }
}
//Copy code


6.2 expansion

In addition to the common method of encapsulating vuex, there are many other things that can also be encapsulated. For example: Paging objects, tabular data, common methods, and so on are not cited one by one. Can see github

If you use it frequently in many places, you can consider encapsulating it as a mixin, but please write a comment. Otherwise someone will scold you behind your back!! You know.

7. optimization

7.1 gzip compression

  • Install compression-webpack-plugin plug-in
npm install compression-webpack-plugin --save-dev
// or
yarn add compression-webpack-plugin --dev
//Copy code


  • Add configuration in vue.config.js
// vue.config.js
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
  chainWebpack: config => {
    // Here is the configuration of the environment, where different environments correspond to different BASE_URL s, so that the request addresses of axios are different.
    config.plugin('define').tap(args => {
      args[0]['process.env'].BASE_URL = JSON.stringify(process.env.BASE_URL)
      return args
    })
    if (process.env.NODE_ENV === 'production') {
      // # region Enables GZip Compression
      config
        .plugin('compression')
        .use(CompressionPlugin, {
          asset: '[path].gz[query]',
          algorithm: 'gzip',
          test: new RegExp('\\.(' + ['js', 'css'].join('|') + ')$'),
          threshold: 10240,
          minRatio: 0.8,
          cache: true
        })
        .tap(args => { })

      // #endregion
    }
  }
}
//Copy code


After npm run build, you can see that the generated. gz file is OK. If your server uses nginx, nginx also needs to be configured to turn on GZIP. Here's how to turn on GZIP in nginx

7.2 Third-party libraries refer to cdn

For the libraries such as vue, vue-router, vuex, axios and element-ui, which are not often changed, we let webpack not pack them. By introducing cdn, we can reduce the size of the code, reduce the bandwidth of the server, cache these files to the client, and the client will load faster.

  • Configure vue.config.js
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
  chainWebpack: config => {
      // Eliminate other code······
      // # region ignores files packaged in the build environment

      var externals = {
        vue: 'Vue',
        axios: 'axios',
        'element-ui': 'ELEMENT',
        'vue-router': 'VueRouter',
        vuex: 'Vuex'
      }
      config.externals(externals)
    const cdn = {
        css: [
          // element-ui css
          '//unpkg.com/element-ui/lib/theme-chalk/index.css'
        ],
        js: [
          // vue
          '//cdn.staticfile.org/vue/2.5.22/vue.min.js',
          // vue-router
          '//cdn.staticfile.org/vue-router/3.0.2/vue-router.min.js',
          // vuex
          '//cdn.staticfile.org/vuex/3.1.0/vuex.min.js',
          // axios
          '//cdn.staticfile.org/axios/0.19.0-beta.1/axios.min.js',
          // element-ui js
          '//unpkg.com/element-ui/lib/index.js'
        ]
      }
      config.plugin('html')
        .tap(args => {
          args[0].cdn = cdn
          return args
        })
      // #endregion
    }
  }
}
//Copy code


  • Modify index.html
<!--public/index.html-->


  
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    &lt;% if (process.env.NODE_ENV === 'production') { %&gt;

      &lt;% for(var css of htmlWebpackPlugin.options.cdn.css) { %&gt;
        <link href="<%=css%>" rel="preload" as="style">
        <link rel="stylesheet" href="<%=css%>" as="style">
      &lt;% } %&gt;
      &lt;% for(var js of htmlWebpackPlugin.options.cdn.js) { %&gt;
        <link href="<%=js%>" rel="preload" as="script">
        <script src="<%=js%>"></script>
      &lt;% } %&gt;
        
    &lt;% } %&gt;
    <title>vue-cli3-project</title>
  
  
    <noscript>
      <strong>We're sorry but vue-cli3-project doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  

//Copy code


7.3 Total Station cdn

We have replaced third-party libraries with cdn, so can we build js,css and other files with cdn?

Apply for your own cdn domain name

If you want to upload your resources to cdn, you have to have your own CDN domain name, if not, you can Qiniuyun official website Apply for registration

  1. Register Qiniuyun Account

  2. To create new storage space in Qiniuyun Object Storage Module

  3. Input Storage Space Information

  4. Determine creation

  5. When created successfully, it will jump to the console page of this storage space.

  6. One of the domain names is your test domain name.

  7. We can upload our js, css and other files in content management, but with so many of our files, it is obviously unreasonable to upload one by one. You're not going to do it.

At this point, these batch and repetitive operations should be launched by our node, let's upload our resource files in batches through node.

Upload the generated js and css resources to qiniu cdn

In Qiniuyun official website Document Center Some people who are interested in how to upload files through node can study it for themselves.

  1. Look at AccessKey and SecretKey in your personal panel - & gt; Key Management, which will be used later.

  1. Install required plug-ins
npm install qiniu glob mime --save-dev
 Copy code


  1. Create an upcdn.js file in the scripts directory
// /scripts/upcdn.js
const qiniu = require('qiniu')
const glob = require('glob')
const mime = require('mime')
const path = require('path')

const isWindow = /^win/.test(process.platform)

let pre = path.resolve(__dirname, '../dist/') + (isWindow ? '\\' : '')

const files = glob.sync(
  `${path.join(
    __dirname,
    '../dist/**/*.?(js|css|map|png|jpg|svg|woff|woff2|ttf|eot)'
  )}`
)
pre = pre.replace(/\\/g, '/')

const options = {
  scope: 'source' // Name of spatial object 
}
var config = {
  qiniu: {
    accessKey: '',  // Access Key in Personal Center Key Management
    secretKey: '',  // SecretKey in Personal Center Key Management
    bucket: options.scope,
    domain: 'http://ply4cszel.bkt.clouddn.com'
  }
}
var accessKey = config.qiniu.accessKey
var secretKey = config.qiniu.secretKey

var mac = new qiniu.auth.digest.Mac(accessKey, secretKey)
var putPolicy = new qiniu.rs.PutPolicy(options)
var uploadToken = putPolicy.uploadToken(mac)
var cf = new qiniu.conf.Config({
  zone: qiniu.zone.Zone_z2
})
var formUploader = new qiniu.form_up.FormUploader(cf)
async function uploadFileCDN (files) {
  files.map(async file =&gt; {
    const key = getFileKey(pre, file)
    try {
      await uploadFIle(key, file)
      console.log(`Upload success key: ${key}`)
    } catch (err) {
      console.log('error', err)
    }
  })
}
async function uploadFIle (key, localFile) {
  const extname = path.extname(localFile)
  const mimeName = mime.getType(extname)
  const putExtra = new qiniu.form_up.PutExtra({ mimeType: mimeName })
  return new Promise((resolve, reject) =&gt; {
    formUploader.putFile(uploadToken, key, localFile, putExtra, function (
      respErr,
      respBody,
      respInfo
    ) {
      if (respErr) {
        reject(respErr)
      }
      resolve({ respBody, respInfo })
    })
  })
}
function getFileKey (pre, file) {
  if (file.indexOf(pre) &gt; -1) {
    const key = file.split(pre)[1]
    return key.startsWith('/') ? key.substring(1) : key
  }
  return file
}

(async () =&gt; {
  console.time('Upload files to cdn')
  await uploadFileCDN(files)
  console.timeEnd('Upload files to cdn')
})()
//Copy code


Modify publicPath

Modify the configuration information of vue.config.js so that its publicPath points to the domain name of our cdn

const IS_PROD = process.env.NODE_ENV === 'production'
const cdnDomian = 'http://ply4cszel.bkt.clouddn.com'
module.exports = {
  publicPath: IS_PROD ? cdnDomian : '/',
  // Eliminate other code·······
}
//Copy code


Modify package.json configuration

Modify the package.json configuration so that we can automatically upload resource files to the cdn server after the build is completed

"build": "vue-cli-service build --mode prod &amp;&amp; node ./scripts/upcdn.js",
Copy code


Running View Effect

npm run build

Then go to your cdn console content management to see if the file has been uploaded successfully

8. docker deployment

The centOS 7 environment is used here, but different systems are used. You can refer to the installation methods of other systems.

8.1 Install docker

  • Update the Software Library
yum update -y
 Copy code


  • Install docker
yum install docker
 Copy code


  • Start docker service
service docker start
 Copy code


  • Install docker-compose
// Install epel source
yum install -y epel-release
 // Install docker-compose
yum install docker-compose
 Copy code


8.2 Write docker-compose.yaml

version: '2.1'
services:
  nginx:
    restart: always
    image: nginx
    volumes:
      #~/ var/local/nginx/nginx.conf is the local directory and/etc/nginx is the container directory
      - /var/local/nginx/nginx.conf:/etc/nginx/nginx.conf
      #~/ var/local/app/dist is the dist directory after the local build, and/usr/src/app is the container directory.
      - /var/local/app/dist:/usr/src/app
    ports:
      - 80:80
    privileged: true
//Copy code


8.3 Write nginx.conf configuration

#user  nobody;

worker_processes  2;

#Work mode and connection number on line
events {
    worker_connections  1024;   #Maximum concurrency of a single working process processing process
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    #The sendfile instruction specifies whether nginx calls the sendfile function (zero copy mode) to output files. For general applications,
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    # Open GZIP
    gzip  on;

    # # Listen on port 80 and forward requests to port 3000
    server {
        #Monitor port
        listen      80;
        #Encoding format
        charset utf-8;

        # Front-end static file resources
        location / {
	    root  /usr/src/app;
            index index.html index.htm;
            try_files $uri $uri/ @rewrites;
        }
        # Configuration will not display 404 if the resource is not matched and the url is pointed to index.html in the history mode of vue-router.
        location @rewrites {
            rewrite ^(.*)$ /index.html last;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}



8.4 Execute docker-compose

docker-compose -d up


8.5 docker + jenkins Automated Deployment

Using docker + jenkins can automatically deploy the environment after code submission to github. This is too much to talk about. The next chapter is updated.

Posted by shadysaiyan on Wed, 25 Sep 2019 03:55:05 -0700