Pick up the time code words…

[VuE-CLI3 upgrade] Old project 50% faster (part 1)

Last time I wrote about error handling of ESLint in the project, forgive me for not being much of an article writer, haha…

Continue to clarify that this article is only a summary of personal experience in the actual work…

In line with the principle of not affecting the business code and the original intention, continue the process of the upgrade and transformation project…

This paper is roughly divided into the following parts:

  • Correlation of environment variables
  • The mock integration
  • npm script
  • Vue.config. js: Webpack optimization, task execution, historical version processing, etc
  • Deploying a remote server using a shell file: Handles historical versions of tasks and pushes them to the remote server

Correlation of environment variables

I have to say that without looking carefully at the documentation, this is a pit…

To view the document

In the VUE-cli3 project, the config directory used to store environment variables was deleted and replaced with:

.env                Load in all environments
.env.local          # is loaded in all environments, but ignored by Git
.env.[mode]         Is loaded only in the specified mode
.env.[mode].local   # is only loaded in the specified mode, but is ignored by Git
Copy the code

There are three environments dev beta prod in the original project, and file.env.dev.env.beta.env. prod is established successively, and environment variables are written in the form of key=value

Special note: Always remember to useVUE_APP_Name the variable at the beginning, otherwise it won’t be written toprocess.env.buildCommand of the time not affected, the building Lord this pit stepped on the very egg pain…

# .env.dev
VUE_APP_API_ENV=dev
VUE_APP_BASE_API=xxx
...
Copy the code

The variable VUE_APP_*, named starting with VUE_APP_, is joyfully accessible in the project using process.env.vue_app_ *.

# .env.beta
NODE_ENV=production
VUE_APP_API_ENV=beta
VUE_APP_BASE_API=xxx
...
Copy the code
# .env.prod
NODE_ENV=production
VUE_APP_API_ENV=pro
VUE_APP_BASE_API=xxx
...
Copy the code

The mock integration

API documentation is still a headache ah, business rapid development, serious document missing, document is still showdoc writing, not fun…

I was going to take the form of a native mock, but think about it, you need to write a bunch of files, and mock files get bigger and bigger as the version goes through iterations…

Finally consider the actual situation and use the form of easy-mock

Create a team project: Log in => My Project (Team Project) => Create team => Create Project

After creation, click to enter the project:

This is the end of the easy mock description. It is easy to use.

Copy the Base URL and write to the previous environment variable file.env.dev

VUE_APP_MOCK=false     															      # Mock global switch
VUE_APP_MOCK_BASE_URL=https://www.easy-mock.com/mock/xxx  # mock base url
Copy the code

VUE_APP_MOCK: As a global switch on whether to enable mock in project dev mode

VUE_APP_MOCK_BASE_URL: As the baseUrl of the requested URL in the project dev pattern

Next look at SRC/API to unify API requests in projects (modular, one-to-one counterpart to back-end microservices modules)

SRC/API /example.js

import { asyncAxios } from '@/plugin/axios'
export const exampleApi = {
  baseUrl: 'example/',
  list (params = {}) {
    return asyncAxios(`The ${this.baseUrl}list`, params, {
      isMock: true
    })
  },
  detail (params = {}) {
    return asyncAxios(`The ${this.baseUrl}detail`, params, {
      isMock: true}}})Copy the code

The asyncAxios method is imported from @/plugin/axios.js. Here is the axios.js code:

import store from '@/store'
import axios from 'axios'
import { Toast } from 'vant'
import util from '@/libs/util'

// Create an error
const errorCreate = msg= > {
  const err = new Error(msg)
  errorLog(err)
  throw err
}

// Record and display errors
const errorLog = err= > {
  // Add to log
  store.dispatch('xxx/log/add', {
    type: 'error',
    err,
    info: 'Data request exception'
  })
  // Print to console
  if (process.env.NODE_ENV === 'development') {
    util.log.danger('>>>>>> Error >>>>>>')
    console.log(err)
  }
  // Display a prompt
  Toast({
    message: err.message,
    type: 'error'})}// Create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 5000 // Request timeout
})

// Request interceptor
service.interceptors.request.use(
  config= > {
    Do some processing before the request is sent
    const token = util.cookies.get('token')
    config.headers['X-Token'] = token
    / / handle the mock
    if (process.env.VUE_APP_MOCK && config.isMock) {
      config.url = `${process.env.VUE_APP_MOCK_BASE_URL}/${config.url}`
    }
    return config
  },
  error => {
    // Failed to send
    console.log(error)
    Promise.reject(error)
  }
)

// Response interceptor
service.interceptors.response.use(
  response= > {
    const dataAxios = response.data
    const { code } = dataAxios
    if(! code)return dataAxios
    switch (code) {
      case 0:
      case 10000:
        / / success
        return dataAxios.data
      case 'xxx':
        errorCreate(`[ code: xxx ] ${dataAxios.msg}: ${response.config.url}`)
        break
      default:
        // Not the correct code
        errorCreate(`${dataAxios.msg}: ${response.config.url}`)
        break
    }
  },
  error => {
    if (error && error.response) {
      switch (error.response.status) {
        case 400: error.message = 'Request error'; break
        case 401: error.message = 'Not authorized, please log in'; break
        case 403: error.message = 'Access denied'; break
        case 404: error.message = 'Error requesting address:${error.response.config.url}`; break
        case 408: error.message = 'Request timed out'; break
        case 500: error.message = 'Server internal error'; break
        case 501: error.message = 'Service not implemented'; break
        case 502: error.message = 'Gateway error'; break
        case 503: error.message = 'Service unavailable'; break
        case 504: error.message = 'Gateway timed out'; break
        case 505: error.message = 'HTTP version not supported '; break
        default: break
      }
    }
    errorLog(error)
    return Promise.reject(error)
  }
)
export default service
Copy the code

The key code for mock is in the request interceptor:

if (process.env.VUE_APP_MOCK && config.isMock) {
	config.url = `${process.env.VUE_APP_MOCK_BASE_URL}/${config.url}`
}
Copy the code

Determine the global Mock switch and the isMock field in the request configuration item to control whether the mock interface is enabled

npm script

Vue-cli-service For more information, see the documentation

Vue-cli-service serve [options] [Entry] Options: --open Open browser on server startup --copy Copy URL to clipped version on server startup --mode Specify environment mode (default: Development) --host specify host (default: 0.0.0.0) --port specify port (default: 8080) -- HTTPS use HTTPS (default: false)Copy the code
Vue cli - service build [options] [entry | pattern] options: - mode specified environment mode (default: production) - dest specifies the output directory (default: Dist) - modern geared to the needs of modern browsers with automatic back to build applications -- target app | lib | wc | wc - async (default: app) - library name or the name of the Web Components mode (default: "Name" field or entry filename in package.json) --no-clean Does not clean the target directory before building the project --report generates report-html to help analyze package contents --report-json generates report-.json To help analyze package contents -- Watch listens for file changesCopy the code

Script configuration in the previous project:

"scripts": {
  "dev": "npm run serve",
  "serve": "vue-cli-service serve --mode dev",
  "build": "vue-cli-service build --no-clean --mode dev",
  "build_app": "cross-env PAGE_ENV=app vue-cli-service build --no-clean --report --mode prod",
  "build_beta": "vue-cli-service build --no-clean --report --mode beta",
  "build_pro": "vue-cli-service build --no-clean --report --mode prod",
  "lint": "vue-cli-service lint --fix"
}
Copy the code

–mode (specify the environment mode), –no-clean (do not clear dist files, will be pushed to the remote server in one click later instructions), –report (generate report. HTML analysis package content), command integration remains the same as the old project…

As if there is nothing left to say in this part, the principle is to maintain the same as the old project command ~~

vue.config.js

To view the document

Go straight to the full code, code words are tired

const path = require('path')
const CompressionWebpackPlugin = require('compression-webpack-plugin')

const assetsDir = 'static'
const resolve = dir= > path.join(__dirname, dir)
// PosiX-compatible processing path
const posixJoin = _path= > path.posix.join(assetsDir, _path)

const lastVersion = new Date().getTime()
const isProd = process.env.NODE_ENV === 'production'

/ / the CDN switch
const OPENCDN = true
const webpackHtmlOptions = {
  // DNS preload, optimize interface request
  dnsPrefetch: [
    'https://aaa.exmaple.com'.'https://bbb.exmaple.com'.'https://ccc.exmaple.com'.'https://ddd.exmaple.com'.'https://eee.exmaple.com'.'https://fff.exmaple.com'].externals: {
    'vue': 'Vue'.'vue-router': 'VueRouter'.'vuex': 'Vuex'.'js-cookie': 'Cookies'
  },
  cdn: {
    // Production environment
    build: {
      css: [
        'https://cdn.jsdelivr.net/npm/[email protected]/lib/index.css'].js: [
        'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js'.'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.min.js'.'https://unpkg.com/[email protected]/dist/vuex.min.js'.'https://cdn.jsdelivr.net/npm/[email protected]/lib/vant.min.js'.'https://cdn.jsdelivr.net/npm/[email protected]/src/js.cookie.min.js']}}}module.exports = {
  publicPath: '/'.outputDir: 'dist',
  assetsDir,
  productionSourceMap: false.// Close the build environment sourceMap
  devServer: {
    open: false.host: '0.0.0.0'.port: 3900
  },
  css: {
    // Add the version numberextract: ! isProd ?false : {
      filename: posixJoin(`css/${lastVersion}-[name].[contenthash:8].css`),
      chunkFilename: posixJoin(`css/${lastVersion}-[name].[contenthash:8].css`)}},configureWebpack: config= > {
    config.resolve.extensions = ['.js'.'.vue'.'.json']
    if (isProd) {
      // The build environment executes the task and writes the version number
      const task = require('./task')
      task.run(lastVersion)
      config.plugins.push(
        / / enable gzip
      	new CompressionWebpackPlugin({
      	  test: new RegExp('\ \. (' + ['js'.'css'].join('|') + '$'),
      	  threshold: 10240.minRatio: 0.8}))// Enable CDN status: externals does not enter webpack
      if (OPENCDN) {
        config.externals = webpackHtmlOptions.externals
      }
    }
  },
  chainWebpack: config= > {
    /** * Remove lazy module prefetch preload, reduce bandwidth pressure */
    config.plugins
      .delete('prefetch')
      .delete('preload')
    config.resolve.alias
      .set('vue$'.'vue/dist/vue.esm.js')
      .set(The '@', resolve('src'))
    // Clear the warning
    config.performance
      .set('hints'.false)
    	// Write the version number to the environment variable
    	config
    	  .plugin('define')
    	  .tap(args= > {
    	    args[0] ['app_build_version'] = lastVersion
    	    return args
    	  })
    config
      .when(isProd, config =>
        // Add version number to production js
        config.output
          .set('filename', posixJoin(`js/${lastVersion}-[name].[chunkhash].js`))
          .set('chunkFilename', posixJoin(`js/${lastVersion}-[id].[chunkhash].js`)))/** * Add CDN parameters to htmlWebpackPlugin configuration, modify public/index.html */
    config.plugin('html').tap(args= > {
      // Write the CDN to webpackHtmlOptions in the production environment and apply it to public/index.html
      if (isProd && OPENCDN) {
        args[0].cdn = webpackHtmlOptions.cdn.build
      }
      // DNS is preloaded
      args[0].dnsPrefetch = webpackHtmlOptions.dnsPrefetch
      return args
    })
  }
}
Copy the code

Here will involve a lot of company business related, make do with it, specially added a note to illustrate… Interested comments discussion

WebpackHtmlOptions application in public/index. The HTML reflect (htmlWebpackPlugin. Read the options) :


      
<html>

<head>
    <title>xxx</title>
    <meta charset="utf-8">
    <meta content="Width = device - width, initial - scale = 1.0, the minimum - scale = 1.0, the maximum - scale = 1.0, user - scalable = no" name="viewport">
    <! -- dns-prefetch, configure in vue.config.js -->
    <% for (var i in htmlWebpackPlugin.options.dnsPrefetch) { %>
    <link rel="dns-prefetch" href="<%= htmlWebpackPlugin.options.dnsPrefetch[i] %>">
    <%} % >
    <meta name="msapplication-tap-highlight" content="no">
    <meta content="telephone=no" name="format-detection" />
    <meta content="email=no" name="format-detection" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
    <meta name="apple-mobile-web-app-title" content="xxx">
    <link rel="icon" href="<%= BASE_URL %>static/applogo.png" type="image/x-icon">
    <! -- CDN CSS, in vue.config.js configuration -->
    <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %>
    <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style">
    <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet">
    <%} % >

    <! -- Use CDN accelerated JS file, configure in vue.config.js -->
    <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
    <link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="preload" as="script">
    <%} % >
</head>

<body>
    <div id="app"></div>
    <! -- < script charset = "utf-8" type = "text/javascript" SRC = "/ / g.alicdn.com/de/prismplayer/2.7.1/aliplayer-min.js" > < / script > -->
    <! -- CDN js, config in vue.config.js -->
    <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
    <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
    <%} % >
</body>

</html>

Copy the code

Task Shell file package remote push

Task.js: Write the history.js version control file in the dist directory using nodejs

Run. Sh: Pull remote code => local package => delete historical files outside version control => Push remote

Why do version control? For users without perception, in order to release at any time, in order not to work overtime (released at any time also add what shift? Very honest, ha ha)…

During the release process, there was no error when users walked around our product.

Build –no-clean mode does not clear the dist folder, history.js stores 5 versions, and build.sh controls 5 versions of the remote repository

Go to the code:

// task.js
let fs = require('fs')
let path = require('path')
let endOfLine = require('os').EOL

module.exports = {
  maxHistoryNum: 5.historyFile: path.resolve(__dirname, './dist/history.js'),
  staticDir: path.resolve(__dirname, './dist/'),

  creataHistoryIfNotExist () {
    if(! fs.existsSync(this.historyFile)) {
      this.storeHistory([], 'a+')}},// @done writes data to history.js
  storeHistory (list, mode) {
    let historyFile = this.historyFile
    let outJson = 'module.exports = [' + endOfLine
    let listLen = list.length
    if (list && listLen > 0) {
      list.forEach((item, index) = > {
        if (index === listLen - 1) {
          outJson += `  ${item}${endOfLine}`
        } else {
          outJson += `  ${item}.${endOfLine}`
        }
      })
    }
    outJson += '] ' + endOfLine

    fs.writeFileSync(historyFile, outJson, {
      flag: mode
    })
  },

  // Recursively delete files in a directory
  rmFiles (dirPath, regexp) {
    let files
    try {
      files = fs.readdirSync(dirPath)
    } catch (e) {
      return
    }
    if (regexp && files && files.length > 0) {
      for (let i = 0; i < files.length; i++) {
        let filename = files[i]
        let filePath = dirPath + '/' + files[i]
        if (fs.statSync(filePath).isFile() && regexp.test(filename)) {
          console.log('Delete expired historical versions ->(' + regexp + ') : + filename)
          fs.unlinkSync(filePath)
        } else {
          this.rmFiles(filePath, regexp)
        }
      }
    }
  },

  // @done
  cleanOldVersionFilesIfNeed (version) {
    let staticDir = this.staticDir
    let maxHistoryNum = this.maxHistoryNum

    let history = []

    try {
      history = require(this.historyFile)
    } catch (e) {
      console.log(e)
    }

    // Add the latest version, delete the old version
    history.push(version)

    // If the number of historical versions exceeds the limit, delete the old version
    let len = history.length
    if (len > maxHistoryNum) {
      let oldVersions = history.slice(0, len - maxHistoryNum)

      for (let i = 0; i < oldVersions.length; i++) {
        let ver = oldVersions[i]
        let reg = new RegExp(ver)
        this.rmFiles(staticDir, reg)
      }

      // Update the history file
      let newVersions = history.slice(len - maxHistoryNum)
      this.storeHistory(newVersions)
    } else {
      // Write the history file
      this.storeHistory(history)
    }
  },

  / / the entry
  run (version) {
    this.creataHistoryIfNotExist()
    this.cleanOldVersionFilesIfNeed(version)
  }
}

Copy the code
# run.sh
# desc: This script is used to build online code with one click and automatically commit it to a remote Git repository
initContext() {# Target file directory directory
    source_dir=dist

	Parameters for packaging the embedded version of the app
	if [ $# -gt&& [0]The $1 = 'beta' ];then
		Production code remote warehouse address
		git_url=xx.git

		# production code local root directory
		dest=".deploy/beta"

		# NPM script rank
		node_script=build_beta
	else
		Production code remote warehouse address
		git_url=xx.git
		
		# production code local root directory
		dest=".deploy/pro"

		# NPM script rank
		node_script=build_pro
	fi
}

Initialize git directory and pull the latest code
init() {echo +++init start;
	
	if[!-d $dest ]; then
	  	git clone $git_url $dest
	fi

	# record the current directory location, the last to return
	cur=`pwd`

  	Go to the git directory
  	cd $dest
  	
  	# git checkout .
  	git add .
  	git stash

  	# reset is the latest version on the line, pull it before resetting it.
  	git pull origin master
  	git reset --hard origin/master
		
	# Then pull again
	git pull origin master

	# return to the original directory
	cd $cur
	echo ---init end;
}

Reset the dist directory
resetDist() {echo +++resetDist start

	rsync -a --delete --exclude='.git' $dest/. ./dist

	echo ---resetDist end
}

# building
build() {echo +++build start
	npm run $node_script
	echo ---build end
}

Check whether it is successful
checkBuild() {if[[!-f $source_dir/index.html || ! -d $source_dir/static ]]; then
		echo error
	else
		echo ok
	fi
}

Copy the code to the $dest directory
cpCode() {echo +++cpCode start
	# Copy code, all files contain hidden files
	rsync -r --delete --exclude='.git'  $source_dir/. $dest

	echo ---cpCode end
}

Commit to a remote Git repository
commit() {echo +++commit start
	# record the current directory location, the last to return
	cur=`pwd`

	Go to the git directory
	cd $dest
	# string submitted
	commit_str="commited in `date '+%Y-%m-%d_%H:%M:%S'`"
	
	git add .
	git commit -am "${commit_str}"
	git push origin master

	# return to the original directory
	cd $cur
	echo ---commit end
}

# Display help information
help() {echo ./run.sh build			"#"Building codeecho ./run.sh init			"#"Initialize the Git repositoryecho ./run.sh commit		"#"Submitted to the gitecho ./run.sh	 			"#"Perform all missionsecho ./run.sh hello			"#"hello
	echo ./run.sh test			"#"test

	echo ./run.sh beta			"#"One-click build and submit beta# App embedded version
	echo---- App embedded version --------echo ./run.sh app			"#"Build and submit app versions with one clickecho---- Help information --------echo ./run.sh help			"#"Help}# For testing
test() {echo "a test empty task"
}

# entry
if [[ $# -lt1 | |The $1 = 'app'  ||  The $1 = 'beta' ||  The $1 = 'beta1' ||  The $1 = 'beta2']].then
	If there is no parameter, select pro package, otherwise select corresponding package
	if [ $# -lt1];then
		type=pro
	else
		type=The $1
	fi
	
	echo===\> Ready to build${type}Version initContext$type && init && resetDist

	# Build code
	buildRes=$(build)

	Check the build results
	echo -e "$buildRes"

	if [[ $buildRes= ~"ERROR"]].then
		echo "$(tput setaf 1)xxx\>build error,task abort$(tput sgr0)"
	else
		Continue until the code builds successfully.
		checkRes=$(checkBuild)
		
		if [ $checkRes= ="ok" ];then
		 	cpCode && commit
			echo "$(tput setaf 2)===\>task complete$(tput sgr0)"
		else
			echo "$(tput setaf 1)xxx\>build error,task abort$(tput sgr0)"
		fi
	fi


elif [ The $1 ]; then
	The parameter is not of package type and is handled by the function
	echo===\> Ready to executeThe ${1}InitContext beta func=The $1
	$func
	echo ===\>task complete
fi

Copy the code

Write version number of history.js:

Package target file DIST:

You can see many version files

This is the end of today’s code word

End of code word +1

(‘ end of today’s code word ‘).repeat(‘999’)

It’s 4:30. I have to catch the high-speed train to attend my cousin’s wedding

At the end

Almost, can end, next article to write about webpack4 things, after all, packaging optimization depends on the stuff ~, enough hard