Vue Study Notes

@[TOC](Table of Contents)

1. Environment Setup

1. Node.js, Npm, Cnpm

Npm depends on Node.js, so download and install it directly, and configure the environment variables. Since I prefer using 'shift+right-click' to launch PowerShell to execute commands, by default, PowerShell does not allow running script files, so the security policy needs to be removed.

set-ExecutionPolicy RemoteSigned

Npm is installed by default in the C drive. To change the default path and view the Npm configuration:

npm config set prefix "E:/Npm"   # Configure the global installation directory
npm config set cache "E:/Npm/npm_cache"   # Configure the cache directory
npm config ls   # View configuration

Due to some known reasons such as slow foreign network speed, I chose to use the Taobao's cnpm for building:

npm install cnpm -g
cnpm install vue
cnpm install --global vue-cli

2. Vue-cli

Directly build a webpack-based project, and some configurations are required.

> vue init webpack project-name

? Project name project-name
? Project description A Vue.js project
? Author Czy <[email protected]>
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? No
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) no

   vue-cli · Generated "project-name".

# Project initialization finished!
# ========================

I chose to install the dependencies manually. However, due to the large size of the generated node_modules and the need for a rebuild in case of any issues, the deletion process is quite slow due to the large number of files. Additionally, I prefer to share dependencies like Maven does, so I used mklink to create directory links. First, copy package.json to a certain directory and execute cnpm i there, which will generate node_modules. After that, execute mklink in the project folder, or create the directory link first then execute cnpm i (i is short for install).

Directory: D:\Project\Library\Modules
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         20/01/09     09:21                node_modules
-a----         20/01/04     14:09             62 mklink.md
-a----         20/01/04     14:07           2707 package.json

PS D:\Project\Library\Modules> npm i

mklink cannot be executed in PowerShell, it needs to be done using CMD, then run npm run dev to start the project normally.

C:\Users\Czy\Desktop\project-name>mklink /J node_modules D:\Project\Library\Modules\node_modules
Junction created for node_modules <<===>> D:\Project\Library\Modules\node_modules
C:\Users\Czy\Desktop\project-name>npm run dev
> [email protected] dev C:\Users\Czy\Desktop\project-name
> webpack-dev-server --inline --progress --config build/webpack.dev.conf.js
 10 13 13 13 13% building modules 33/37 modules 4 active ...ct-name\src\components\HelloWorld.vue{ parser: "babylon" } is deprecated; we now treat it as {  14 14 95% emitting
 DONE  Compiled successfully in 14788ms                                                                       1:09:13 PM
 I  Your application is running here: http://localhost:8080

II. Vue Instance

1. Directory Structure

vue-cli
   ├── build/                # Webpack configuration directory
   ├── dist/                 # Production environment project generated by build
   ├── config/               # Vue basic configuration files, can set listening ports, package output, etc.
   ├── node_modules/         # Dependency packages, generated by 'cnpm i'
   ├── src/                  # Source code directory (build application focuses on this directory)
   │   ├── assets/           # Place static files that need to be processed by Webpack, usually stylesheets such as CSS, SASS, and some external JS files
   │   ├── components/       # Component directory
   │   ├── filters/          # Filters
   │   ├── store/            # State management
   │   ├── routes/           # Routes, configure project routes here
   │   ├── utils/            # Utility classes
   │   ├── views/            # Route page components
   │   ├── App.vue           # Root component
   │   └── main.js           # Entry file
   ├── index.html            # Main page, will be injected with Vue after opening the page
   ├── static/               # Place static files that do not need to be processed by Webpack, usually for images and other resources
   ├── .babelrc              # Babel transcode configuration
   ├── .editorconfig         # Code format
   ├── .eslintignore         # ESLint ignore
   ├── .eslintrc             # ESLint configuration
   ├── .gitignore            # Git ignore
   ├── package.json          # Configuration information for this project, startup method
   ├── package-lock.json     # Records the specific source and version numbers of each npm package actually installed in the current status
   └── README.md             # Project description

2. Lifecycle

IMG

3. Filters

Filters can filter data, for example, displaying 1 as OK in a printed table.

//Used in templates
{{status | statusFilter}} //Use {{ data | filter definition}} Support chaining {{ data | filter definition1 | filter definition2}}

//Can be referenced in style
:style="status | colorFilter"
{
  "plugins": [["component", [
    {
      "libraryName": "element-ui",
      "styleLibraryName": "theme-chalk"
    }
  ]]]
}
import { 
  Input, 
  Button 
} from 'element-ui';

Vue.use(Input);
Vue.use(Button);

3.引用组件

在定义的组件加入组件属性

import myfile from './file'

export default{
  components: { 
    'my-file': myfile 
  }
}
cnpm i babel-plugin-component -D

.babelrc

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2",
    ["es2015", { "modules": false }]
  ],
  "plugins": ["transform-vue-jsx", "transform-runtime",[
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]]
}

main.js

import { Menu, MenuItem } from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(Menu)
Vue.use(MenuItem)

3. Creating Custom Components

Similar to these component libraries, use as an element in the component, you need a .vue file to create the custom component, and index.js to expose the interface

index.js

import layout from './layout';
/* istanbul ignore next */
layout.install = function(Vue) {
  Vue.component(layout.name, layout);
};
export default layout;

Then import and mount in main.js

import layout from '@/components/common/layout';
Vue.use(layout)

Four, Router Route

1. Router Configuration

// Configuration as follows

import Vue from 'vue'
import Router from 'vue-router'
// Import page components
import Index from "@/components/login/Index.vue"
import ManagerIndex from "@/components/manager/Index.vue"
// Mount Router
Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',                        // Page path /#/
      name: "Index",                    // Page naming, when passing parameters to navigate to a page, use name
      component: Index,                 // Load component
    },
    {
      path: '/ManagerIndex',
      name: "ManagerIndex",
      component: ManagerIndex,
      meta:{                             // Some configuration information
        auth: true                       // Configure this page to require authentication, needs to be used with router.beforeEach()
      },
      children: [                        // Child components of this component
        {
          path: 'OverView',              // Page path /#/ManagerIndex/OverView
          name: "OverView",
          component: OverView
        }
        ]
    }
  ]
})

router-link has a to property, which is used for anchor jumping, and router-view is a container for loading the components defined in Router based on the anchor.

<router-link :to="/">Go directly to</router-link>
<router-link :to="{ path: '/path', query: { id: 123 }}">Query Parameters</router-link>
<router-link :to="{ name: 'routername', params: { id: 123 }}">Parameter, using name, indicate placeholder :id in path</router-link>
<router-view></router-view>

router-link provides declarative navigation, while Router provides programmatic navigation

this.$router.push({ name: 'OverView'})

3. keep-alive

When router-link navigates, the component will be dynamically created and destroyed. If you want to maintain the component state, you can use <keep-alive/>. Note that this will not trigger the component lifecycle.

<keep-alive><router-view></router-view></keep-alive>

4. Navigation Guards

When registering routes, an auth is declared in meat to perform authentication. It needs to be used in conjunction with router.beforeEach() and global variables. Declare this method in main.js to perform navigation guards. Be sure to call the next() method, otherwise the navigation will not be executed.

router.beforeEach((to, from, next) => {
  if (to.meta.auth && !Vue.prototype.$globalData.user) {
    next({
      path: "/"
    })
  } else {
    next();
  }
})

Five, Method Encapsulation

1. Global Variables

Declared and exported in dispose.js

const $globalData = {
  user: 0,
  url: "http://dev.touchczy.top/",
  header: {
    'content-type': 'application/x-www-form-urlencoded'
  }
}
export default {
  $globalData: $globalData
}

Import and extend Vue prototype in main.js

import dispose from '@/vector/dispose'
Vue.prototype.$globalData = dispose.$globalData;

2. Modal Encapsulation

Import components in dispose.js, encapsulate loading and alert, and export them

import {
  Message,
  Loading
} from 'element-ui'

function startLoading(options) {
  if (!options.load) return true;
  var loadingInstance = Loading.service({
    lock: true,
    text: 'loading...'
  })
  return loadingInstance;
}

function endLoading(options, loadingInstance) {
  if (!options.load) return true;
  loadingInstance.close();
}

function toast(msg, type = 'error') {
  Message({
    message: msg,
    type: type,
    duration: 2000,
    center: true
  })
}
export default {
  $toast: toast
}

main.js
import dispose from '@/vector/dispose'
Vue.prototype.$toast = dispose.$toast;

3. Shallow Copy and Deep Copy

function extend() {
  var aLength = arguments.length;
  var options = arguments[0];
  var target = {};
  var copy;
  var i = 1;
  if (typeof options === "boolean" && options === true) {
    // Deep copy (only recursively process objects)
    for (; i < aLength; i++) {
      if ((options = arguments[i]) != null) {
        if (typeof options !== 'object') {
          return options;
        }
        for (var name in options) {
          copy = options[name];
          if (target === copy) {
            continue;
          }
          target[name] = this.extend(true, options[name]);
        }
      }
    }
  } else {
    // Shallow copy
    target = options;
    if (aLength === i) {
      target = this;
      i--;
    } // If there is only one parameter, extend the function. If there are more than two parameters, add the subsequent objects to the first object
    for (; i < aLength; i++) {
      options = arguments[i];
      for (var name in options) {
        target[name] = options[name];
      }
    }
  }
  return target;
}

4. Encapsulation of axios

Since I just started learning Vue, I still prefer to use success, fail, and complete for network requests. I have encapsulated the request to load Loading when sending a request, pop up an error message if the request fails, and return a promise object so that then() and other methods can still be used. When making a post request, I use { 'content-type': 'application/x-www-form-urlencoded'} as the request header, and in transformRequest I convert json format requests to form requests.

function ajax(requestInfo) {
  var options = {
    load: true,
    url: "",
    method: "GET",
    data: {},
    param: {},
    success: () => {},
    fail: function() { this.completeLoad = () => {toast("Server error", 'error');} },
    complete: () => {},
    completeLoad: () => {}
  };
  extend(options, requestInfo);
  let loadingInstance = startLoading(options);
  return axios.request({
    url: options.url,
    data: options.data,
    params: options.param,
    method: options.method,
    headers: $globalData.header,
    transformRequest: [function(data) {
      let ret = ''
      for (let it in data) ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
      return ret
    }]
  }).then(function(res) {
    try {
      options.success(res);
    } catch (e) {
      options.completeLoad = () => {
        toast("PARSE ERROR");
      }
      console.warn(e);
    }
  }).catch(function(res) {
    options.fail(res);
  }).then(function(res) {
    endLoading(options, loadingInstance);
    try {
      options.complete(res);
    } catch (e) {
      console.warn(e);
    }
    options.completeLoad(res);
  })
}
export default {
  $ajax: ajax
}

main.js

import dispose from '@/vector/dispose'
Vue.prototype.$ajax = dispose.$ajax;

When testing, if you need to handle cross-origin requests and use cookies, first declare axios.defaults.withCredentials = true. At this point, the backend cannot set Access-Control-Allow-Origin to *. axios

axios.defaults.withCredentials = true

PHP

header('Content-Type: text/html;charset=utf-8');
header('Access-Control-Allow-Origin:http://localhost:8080'); // Allow website requests
header('Access-Control-Allow-Methods:POST,GET,OPTIONS,DELETE'); // Allow request types
header('Access-Control-Allow-Credentials: true'); // Set whether cookies are allowed to be sent
header('Access-Control-Allow-Headers: Content-Type,Content-Length,Accept-Encoding,X-Requested-with, Origin'); // Set the fields for allowing custom request headers

Six. Webpack

1. build/build.js

// Build production version node build/build.js
require('./check-versions')()   // call the file check-versions to check the versions and execute this function directly
process.env.NODE_ENV = 'production'   // Register on the window global variable to distinguish between production and development environments; this is the production environment
const ora = require('ora')   // Terminal display of spinning loading
const rm = require('rimraf')   // Library for the rm -rf command in the Node environment
const path = require('path')   // File path processing library
const chalk = require('chalk')   // Terminal display of text with colors
const webpack = require('webpack')   // webpack
const config = require('../config')   // Import configuration
const webpackConfig = require('./webpack.prod.conf')   // Import configuration for the production environment

const spinner = ora('building for production...')   // Terminal display of the build process
spinner.start()   // Terminal display of loading

rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {   // Delete the compiled files, i.e., the dist folder
  if (err) throw err
  webpack(webpackConfig, (err, stats) => {   // Start compiling in the callback function after deletion
    spinner.stop()   // Terminal stop loading
    if (err) throw err
    process.stdout.write(stats.toString({   // After compilation, output the compiled files in the terminal
      colors: true,
      modules: false,
      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
      chunks: false,
      chunkModules: false
    }) + '\n\n')
    /* ... */ // The following is the output of the compilation result
  })
})

2. build/check-version.js

const chalk = require('chalk')   // Terminal display of text with colors
const semver = require('semver') // Version checking
const packageConfig = require('../package.json') // Read the project configuration file
const shell = require('shelljs') // shell
function exec (cmd) {   //return a newly created child process through the child_process module, execute the Unix system command and convert it to a string without spaces
  return require('child_process').execSync(cmd).toString().trim()
}

const versionRequirements = [
  {
    name: 'node',
    currentVersion: semver.clean(process.version),   //use semver to format the version
    versionRequirement: packageConfig.engines.node   //get the node version set in package.json
  }
]

if (shell.which('npm')) {
  versionRequirements.push({
    name: 'npm',
    currentVersion: exec('npm --version'),   //call the npm --version command and pass the parameter to the exec function to get the clean version number
    versionRequirement: packageConfig.engines.npm
  })
}

module.exports = function () {
  /* ... */   //warning or error message
}

3. build/utils.js

//process css
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const packageConfig = require('../package.json')

exports.assetsPath = function (_path) {   //export the location of the file and determine whether it is in the development environment or production environment based on the environment, according to the build.assetsSubDirectory or dev.assetsSubDirectory defined in the index.js file in the config folder
  const assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory
  return path.posix.join(assetsSubDirectory, _path) //the path module provides some tools for handling file paths
}

exports.cssLoaders = function (options) {
  options = options || {}
  const cssLoader = { //use css-loader and postcssLoader, use the options.usePostCSS property to determine whether to use methods such as compression in postcssLoader
    loader: 'css-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }
const postcssLoader = {
  loader: 'postcss-loader',
  options: {
    sourceMap: options.sourceMap
  }
}
function generateLoaders(loader, loaderOptions) {
  const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
  if (loader) {
    loaders.push({
      loader: loader + '-loader',
      options: Object.assign({}, loaderOptions, {   // Object.assign is a shallow copy in ES6 syntax, and the last two are merged and copied to complete the assignment.
        sourceMap: options.sourceMap
      })
    })
  }

  if (options.extract) {
    return ExtractTextPlugin.extract({   // ExtractTextPlugin can extract text, representing the use of the above processed loaders first, and using vue-style-loader when not correctly introduced
      use: loaders,
      fallback: 'vue-style-loader'
    })
  } else {
    return ['vue-style-loader'].concat(loaders)   // return the final value of vue-style-loader connection loaders
  }
}
return {
  css: generateLoaders(), // need css-loader and vue-style-loader
  postcss: generateLoaders(), // need css-loader and postcssLoader and vue-style-loader
  less: generateLoaders('less'), // need less-loader and vue-style-loader
  sass: generateLoaders('sass', { indentedSyntax: true }), // need sass-loader and vue-style-loader
  scss: generateLoaders('sass'), // need sass-loader and vue-style-loader
  stylus: generateLoaders('stylus'), // need stylus-loader and vue-style-loader
  styl: generateLoaders('stylus') // need stylus-loader and vue-style-loader
}
} exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {  // combine various css, less, sass, etc. to get the result and output the output
  const loader = loaders[extension]
  output.push({
    test: new RegExp('\\.' + extension + '$'),
    use: loader
  })
}

return output
}

exports.createNotifierCallback = () => {
  const notifier = require('node-notifier') // send notifications to the cross-platform notification system
  return (severity, errors) => {
    if (severity !== 'error') return
    const error = errors[0]
    const filename = error.file && error.file.split('!').pop()
    notifier.notify({   // when an error occurs, output the title of the error message, error message details, subtitle, and icon
      title: packageConfig.name,
      message: severity + ': ' + error.name,
      subtitle: filename || '',
      icon: path.join(__dirname, 'logo.png')   // used to join paths and will correctly use the path separator of the current system, "/" for Unix and "\" for Windows
    })
  }
}
// Processing .vue files, parsing each language block (template, script, style) in this file and converting it into a JS module that can be used by JS.
const utils = require('./utils')
const config = require('../config')
const isProduction = process.env.NODE_ENV === 'production'
const sourceMapEnabled = isProduction
  ? config.build.productionSourceMap
  : config.dev.cssSourceMap

module.exports = {   // Processing CSS files in the project, enabling source map by default in production and testing environments, while extracting styles into separate files only in the production environment.
  loaders: utils.cssLoaders({
    sourceMap: sourceMapEnabled,
    extract: isProduction
  }),
  cssSourceMap: sourceMapEnabled,
  cacheBusting: config.dev.cacheBusting,
  transformToRequire: {   // During template compilation, the compiler can convert certain attributes, such as src paths, into require calls so that the target resource can be processed by webpack.
    video: ['src', 'poster'],
    source: 'src',
    img: 'src',
    image: 'xlink:href'
  }
}

5. build/webpack.base.conf.js

// The base configuration file extracted for common use in development and production, mainly implementing entry configuration, output environment configuration, module resolution configuration, and plugins.
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')

function resolve (dir) {   // Concatenates the absolute path.
  return path.join(__dirname, '..', dir)
}
module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {   // Entry file, there can be multiple entries or just one, by default, only one entry 'app' for single page applications
    app: './src/main.js'
  },
  output: {    // Output configuration, the default target folder path is /dist
    path: config.build.assetsRoot,   // Path
    filename: '[name].js',   // Output file name
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],  // Automatically extend file suffixes, e.g., if it's a js file, you can omit the .js when referencing
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),   // @ is equivalent to /src
    }
  },
  module: {   // Use plugins to configure the processing method for corresponding files
    rules: [{   
        test: /\.vue$/,
        loader: 'vue-loader',   // Use vue-loader to transform vue files into js modules
        options: vueLoaderConfig
      },{
        test: /\.js$/,
        loader: 'babel-loader',   // js files need to be compiled into es5 files and compressed using babel-loader
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
      },{
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',   // Images, audio-visual, and fonts are all handled using url-loader, files over 10000 will be compiled into base64
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },{
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },{
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  node: {   // The following options are Node.js global variables or modules, primarily to prevent webpack from injecting Node.js modules into Vue
    setImmediate: false,
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }
}
// Development environment webpack related configuration
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')   // Implement inheritance of webpack.base.config.js by webpack.dev.conf.js through webpack-merge
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')   // Plugin to beautify webpack error messages and logs
const portfinder = require('portfinder')   // By default, search for the location of the idle port, searching for port 8000

const HOST = process.env.HOST   // process is a global object of node to access the current program's environment variable, i.e., HOST
const PORT = process.env.PORT && Number(process.env.PORT)

const devWebpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })   // Rules are style loaders processed in the utils tool, generating rules for css, less, postcss, etc.
  },
  devtool: config.dev.devtool,   // Enhanced debugging
devServer: {   // All the configurations here are set in the config's index.js
  clientLogLevel: 'warning',
  historyApiFallback: {   // When using the HTML5 History API, any 404 responses can be replaced with index.html
    rewrites: [
      { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
    ],
  },
  hot: true,   // Hot reload
  contentBase: false,
  compress: true,   // Compression
  host: HOST || config.dev.host,
  port: PORT || config.dev.port,
  open: config.dev.autoOpenBrowser,   // Automatically open the browser when debugging
  overlay: config.dev.errorOverlay
    ? { warnings: false, errors: true }
    : false,
  publicPath: config.dev.assetsPublicPath,
  proxy: config.dev.proxyTable,   // Interface proxy
  quiet: true,   // Whether to disable printing warnings and errors in the console, if using FriendlyErrorsPlugin, this will be true
  watchOptions: {
    poll: config.dev.poll,   // File system change detection
  }
},
plugins: [
  new webpack.DefinePlugin({
    'process.env': require('../config/dev.env')
  }),
  new webpack.HotModuleReplacementPlugin(),   // Module hot replacement plugin, no need to refresh the page when modifying modules
  new webpack.NamedModulesPlugin(),   // Display the correct name of files
  new webpack.NoEmitOnErrorsPlugin(),   // When there is a webpack compilation error, it stops the packaging process to prevent erroneous code from being packaged into files
  new HtmlWebpackPlugin({   // This plugin can automatically generate an HTML5 file or inject the compiled code into a template file
    filename: 'index.html',
    template: 'index.html',
    inject: true
  }),
  new CopyWebpackPlugin([   // Copy plugin
    {
      from: path.resolve(__dirname, '../static'),
      to: config.dev.assetsSubDirectory,
      ignore: ['.*']
    }
  ])
]
})

module.exports = new Promise((resolve, reject) => {
  portfinder.basePort = process.env.PORT || config.dev.port
  portfinder.getPort((err, port) => {   // Find the port number
    if (err) {
      reject(err)
    } else {   // When the port is occupied, reset the port for evn and devServer
      process.env.PORT = port
      devWebpackConfig.devServer.port = port
      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
        compilationSuccessInfo: {
          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
        },
        onErrors: config.dev.notifyOnErrors
        ? utils.createNotifierCallback()
        : undefined
      }))
      resolve(devWebpackConfig)
    }
  })
})
// Configuration file related to production environment in webpack
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')   // Plugin for merging webpack configurations
const baseWebpackConfig = require('./webpack.base.conf')   // Basic webpack configuration
const CopyWebpackPlugin = require('copy-webpack-plugin')   // Plugin for copying files and folders in webpack
const HtmlWebpackPlugin = require('html-webpack-plugin')   // Plugin for generating and injecting HTML into .html files
const ExtractTextPlugin = require('extract-text-webpack-plugin')   // Plugin for extracting CSS
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')   // Plugin for optimizing and compressing CSS in webpack
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

const env = require('../config/prod.env')
const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({   
      sourceMap: config.build.productionSourceMap,   // Enable debug mode. Default is true
      extract: true,
      usePostCSS: true
    })
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {   // Compress
          warnings: false
        }
      },
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    new ExtractTextPlugin({   // Extract text, for example, styles inserted on the index page after packaging are extracted by this plugin to reduce requests
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      allChunks: true,
    }),
    new OptimizeCSSPlugin({   // CSS optimization plugin
      cssProcessorOptions: config.build.productionSourceMap
        ? { safe: true, map: { inline: false } }
        : { safe: true }
    }),
    new HtmlWebpackPlugin({   // HTML packaging
      filename: config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,   // Remove comments
        collapseWhitespace: true,   // Remove spaces
        removeAttributeQuotes: true   // Remove attribute quotes
      },
      chunksSortMode: 'dependency' // Module sort, in the order we need
    }),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new webpack.optimize.CommonsChunkPlugin({   // Extract common modules
      name: 'vendor',
      minChunks (module) {
        // any required modules inside node_modules are extracted to vendor
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),

    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),
new CopyWebpackPlugin([   // For example, after packaging, the packaged files need to be copied to the dist directory
  {
    from: path.resolve(__dirname, '../static'),
    to: config.build.assetsSubDirectory,
    ignore: ['.*']
  }
])
]

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')
  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}
if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig

8. config/dev.env.js

// Files in the config folder serve the build
const merge = require('webpack-merge')   // webpack-merge provides a merge function, which creates a new object by merging arrays and merged objects, meeting functions and executing them, and encapsulating the return values in functions. Here, dev and prod are merged
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
  NODE_ENV: '"development"'
})

9. config/index.js

// Configuration files are used to define parameters needed in development and production environments
const path = require('path')
module.exports = {
  dev: {   // Configuration under the development environment
    assetsSubDirectory: 'static',   // Subdirectory, generally for storing css, js, image and other files
    assetsPublicPath: '/',   // Root directory
    proxyTable: {},   // This property can be used to solve cross-domain problems
    host: 'localhost', // Service startup address
    port: 8080, // Service startup port
    autoOpenBrowser: false,   // Whether to open the browser automatically
    errorOverlay: true,   // Browser error prompt
    notifyOnErrors: true,   // Cross-platform error prompt
    poll: false,  // Notify devServer.watchOptions of file system changes
    devtool: 'cheap-module-eval-source-map',   // Adding debugging. This property is for original source code (line only) and cannot be used in production environments
    cacheBusting: true,   // Invalidating the cache
    cssSourceMap: true   // It will be very difficult to locate bugs after code compression, so sourcemap is introduced to record the location information before and after compression. When an error occurs, the position before compression can be directly located
  },
build: {    // Configuration for production environment
    index: path.resolve(__dirname, '../dist/index.html'),   // The location and name of the compiled index file, change the suffix as needed, such as index.php
    assetsRoot: path.resolve(__dirname, '../dist'),   // Location for storing compiled production environment code
    assetsSubDirectory: 'public/vue-app/index',   // Folder name for storing js, css, images, etc.
    assetsPublicPath: '/',   // Root directory for publication as the absolute path of the web container, change to ./ for relative path
    productionSourceMap: true,
    devtool: '#source-map',
    productionGzip: false,   // Gzip command in unit is used to compress files. In gzip mode, the file extensions to be compressed are js and css
    productionGzipExtensions: ['js', 'css'],
    bundleAnalyzerReport: process.env.npm_config_report
  }
}

10. config/prod.env.js

// Use prod.env.js for production environment configuration during deployment
module.exports = {
  NODE_ENV: '"production"'
}

11. Configuration Proxy to Solve Cross-Origin Issues

// Configure proxyTable in config/index.js
proxyTable: {
  '/':{
    target: "http://www.xxx.com",   // the address to be accessed
    changeOrigin: true   // enable cross-origin
  }
}
// The configuration can be understood as proxying access to / as http://www.xxx.com to avoid triggering cross-origin issues and being intercepted by the browser.
// In Vue requests to access the http://www.xxx.com/testData interface can be written directly as requests to /testData
// Use process.env.NODE_ENV to differentiate between development and production environment when requesting URLs