From Vue cli to package Publishing

Structure and description of Vue cli initial directory

├── README.md
 ├├ -- build the code of compiling task
│   ├── build.js
│   ├── check-versions.js
│   ├── dev-client.js
│   ├── dev-server.js
│   ├── utils.js
│   ├── webpack.base.conf.js
│   ├── webpack.dev.conf.js
│   └── webpack.prod.conf.js
 ├ - configuration file of config webpack
│   ├── dev.env.js
│   ├── index.js
│   ├── prod.env.js
 └ -- test.env.js added by itself, test environment
├── index.html
 ├ -- basic information of package.json project
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── Hello.vue
│   └── main.js
└── static

Entrance

From package.json, we can see that

"scripts": {
    "dev": "node build/dev-server.js",
    "build": "node build/build.js",
    "lint": "eslint --ext .js,.vue src"
}

When we execute npm run dev / npm run build, we run node build/dev-server.js or node build/build.js

build/dev-server.js

// Check Node and npm versions
require('./check-versions')()
// Get the default configuration of config/index.js
var config = require('../config')

// If the environment of Node cannot judge whether it is currently a dev / product environment
// Use config.dev.env.node'env as the current environment
if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
// Use the file path tool of NodeJS
var path = require('path')
// Using express
var express = require('express')
// Using webpack
var webpack = require('webpack')
// A plug-in that can force the browser to open and jump to the specified url
var opn = require('opn')
// Using proxyTable
var proxyMiddleware = require('http-proxy-middleware')

// webpack configuration using dev environment
var webpackConfig = require('./webpack.dev.conf')
// default port where dev server listens for incoming traffic
// If no run port is specified, use config.dev.port as the run port
var port = process.env.PORT || config.dev.port
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
// Use the configuration of config.dev.proxyTable as the proxy configuration of proxyTable
var proxyTable = config.dev.proxyTable
// Start a service with express
var app = express()
// Start webpack for compilation
var compiler = webpack(webpackConfig)
// Start webpack dev middleware and store the compiled files in memory
var devMiddleware = require('webpack-dev-middleware')(compiler, {
  publicPath: webpackConfig.output.publicPath,
  stats: {
    colors: true,
    chunks: false
  }
})
// Start webpack hot middleware, which we often call hot reload
var hotMiddleware = require('webpack-hot-middleware')(compiler)
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
    hotMiddleware.publish({ action: 'reload' })
    cb()
  })
})
// proxy api requests
// Hang the request configuration in proxyTable on the started express service
Object.keys(proxyTable).forEach(function (context) {
  var options = proxyTable[context]
  if (typeof options === 'string') {
    options = { target: options }
  }
  app.use(proxyMiddleware(context, options))
})
// handle fallback for HTML5 history API
// Use connect history API fallback to match resources and redirect to the specified address if they don't match
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
// Hang the compiled web pack file temporarily stored in memory on the express service
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
// Hang hot reload on the express service
app.use(hotMiddleware)
// serve pure static assets
// Static resource path for splicing static folders
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
// Provide response services for static resources
app.use(staticPath, express.static('./static'))
// Let's use the express service to listen for port requests and expose it as an interface of dev-server.js
module.exports = app.listen(port, function (err) {
  if (err) {
    console.log(err)
    return
  }
  var uri = 'http://localhost:' + port
  console.log('Listening at ' + uri + '\n')
  // when env is testing, don't need open it
  // If it is not a test environment, automatically open the browser and jump to our development address
  if (process.env.NODE_ENV !== 'testing') {
    opn(uri)
  }
})

webpack.dev.conf.js

// config/index.js is also used
var config = require('../config') 
// Using webpack
var webpack = require('webpack') 
// Using webpack to configure the merge plug-in
var merge = require('webpack-merge') 
// Use some gadgets
var utils = require('./utils') 
// Load webpack.base.conf
var baseWebpackConfig = require('./webpack.base.conf') 
// Using the html web pack plugin plug-in, this plug-in can help us automatically generate html and inject it into the. html file
var HtmlWebpackPlugin = require('html-webpack-plugin') 
// add hot-reload related code to entry chunks
// Add the hol reload relative path to the corresponding entry of webpack.base.conf
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
  baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
// Combine the configuration of our webpack.dev.conf.js with that of webpack.base.conf.js
module.exports = merge(baseWebpackConfig, {
  module: {
    // Using styleLoaders
    loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
  },
  // eval-source-map is faster for development
  // Use the "Eval source map" mode as a development tool. Refer to previous articles of DDFE for details of this configuration
  devtool: '#eval-source-map',
  plugins: [

    // definePlugin receives strings and inserts them into the code, so you can write JS strings if you want
    new webpack.DefinePlugin({
      'process.env': config.dev.env
    }),
    // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
    new webpack.optimize.OccurenceOrderPlugin(),

    // HotModule plug-in will only return to the corresponding page module when the page is changed, and will not redraw the entire html file
    new webpack.HotModuleReplacementPlugin(),

    // When NoErrorsPlugin is used, the error reporting in the page will not be blocked, but it will be reported after compilation
    new webpack.NoErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin

    // Take index.html as the entry, inject HTML code and generate index.html file
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    })
  ]
})

webpack.base.conf.js

// Use the file path plug-in of NodeJS
var path = require('path') 
// Import config/index.js
var config = require('../config') 
// Introduce some gadgets
var utils = require('./utils') 
// Splicing our workspace path into an absolute path
var projectRoot = path.resolve(__dirname, '../') 
// Use NodeJS environment as our compilation environment
var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
// various preprocessor loaders added to vue-loader at the end of this file
// Whether to open cssSourceMap in dev environment, which can be configured in config/index.js
var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
// Whether to open cssSourceMap in the production environment, which can be configured in config/index.js
var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
// Whether to use cssourcemap finally
var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
module.exports = {
  entry: {
    // Compile file entry
    app: './src/main.js' 
  },
  output: {
    // The root path of the compiled output
    path: config.build.assetsRoot, 
    // Publishing path of compiled output in formal publishing environment
    publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, 
    // Filename of the compiled output
    filename: '[name].js' 
  },
  resolve: {
    // Autocomplete extension
    extensions: ['', '.js', '.vue'],
    // Files or folders that are not automatically completed or processed
    fallback: [path.join(__dirname, '../node_modules')],
    alias: {
      // The default path agent, such as import Vue from 'vue', will automatically find it in 'vue/dist/vue.common.js'
      'vue$': 'vue/dist/vue.common.js',
      'src': path.resolve(__dirname, '../src'),
      'assets': path.resolve(__dirname, '../src/assets'),
      'components': path.resolve(__dirname, '../src/components')
    }
  },
  resolveLoader: {
    fallback: [path.join(__dirname, '../node_modules')]
  },
  module: {
    preLoaders: [
      // Preprocessed files and used loader s
      {
        test: /\.vue$/,
        loader: 'eslint',
        include: projectRoot,
        exclude: /node_modules/
      },
      {
        test: /\.js$/,
        loader: 'eslint',
        include: projectRoot,
        exclude: /node_modules/
      }
    ],
    loaders: [
      // Files to be processed and loader s to be used
      {
        test: /\.vue$/,
        loader: 'vue'
      },
      {
        test: /\.js$/,
        loader: 'babel',
        include: projectRoot,
        exclude: /node_modules/
      },
      {
        test: /\.json$/,
        loader: 'json'
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url',
        query: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url',
        query: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  eslint: {
    // eslint code check configuration tool
    formatter: require('eslint-friendly-formatter')
  },
  vue: {
    // . vue file configuration loader and tools (autoprefixer)
    loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }),
    postcss: [
      require('autoprefixer')({
        browsers: ['last 2 versions']
      })
    ]
  }
}

config/index.js

index.js There are dev and production Configuration of two environments


// see http://vuejs-templates.github.io/webpack for documentation.
// No more repetition
var path = require('path')
module.exports = {
  // production environment
  build: { 
    // Using the compile environment defined in config/prod.env.js
    env: require('./prod.env'), 
    index: path.resolve(__dirname, '../dist/index.html'), // Compile the input index.html file
    // Static resource root path for compilation output
    assetsRoot: path.resolve(__dirname, '../dist'), 
    // Compiled output secondary directory
    assetsSubDirectory: 'static', 
    // Compile and publish the root directory of the online path, which can be configured as resource server domain name or CDN domain name
    assetsPublicPath: '/', 
    // Whether to open cssourcemap
    productionSourceMap: true, 
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    // Open gzip or not
    productionGzip: false, 
    // File extensions that need to be compressed using gzip
    productionGzipExtensions: ['js', 'css'] 
  },
  // dev environment
  dev: { 
    // Using the compile environment defined in config/dev.env.js
    env: require('./dev.env'), 
    // Port to run the test page
    port: 8080, 
    // Compiled output secondary directory
    assetsSubDirectory: 'static', 
    // Compile and publish the root directory of the online path, which can be configured as resource server domain name or CDN domain name
    assetsPublicPath: '/', 
    // Interface requiring proxyTable proxy (cross domain)
    proxyTable: {}, 
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    // Whether to open cssourcemap
    cssSourceMap: false 
  }
}

So far, our npm run dev command has been explained. Let's take a look at what happens when we execute the npm run build command

build.js

// https://github.com/shelljs/shelljs
// Check Node and npm versions
require('./check-versions')() 
// Using the shell js plug-in, we can use shell in js of node environment
require('shelljs/global') 
env.NODE_ENV = 'production'
// No more details
var path = require('path') 
// Load config.js
var config = require('../config') 
// A good-looking loading plug-in
var ora = require('ora') 
// Load webpack
var webpack = require('webpack') 
// Load webpack.prod.conf
var webpackConfig = require('./webpack.prod.conf') 
//  Output prompt information ~ prompt the user to view this page under http service, otherwise it is a blank page
console.log(
  '  Tip:\n' +
  '  Built files are meant to be served over an HTTP server.\n' +
  '  Opening index.html over file:// won\'t work.\n'
)
// Using ora to print out loading + log
var spinner = ora('building for production...') 
// Start loading animation
spinner.start() 
// Concatenate compile output file path
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
// Delete this folder (recursive delete)
rm('-rf', assetsPath)
// Create this folder 
mkdir('-p', assetsPath)
// Copy the static folder to our compile output directory
cp('-R', 'static/*', assetsPath)
//  Start compilation of webpack
webpack(webpackConfig, function (err, stats) {
  // Callback function compiled successfully
  spinner.stop()
  if (err) throw err
  process.stdout.write(stats.toString({
    colors: true,
    modules: false,
    children: false,
    chunks: false,
    chunkModules: false
  }) + '\n')
})

webpack.prod.conf.js

// No more details
var path = require('path')
// Load confi.index.js
var config = require('../config')
// Use some gadgets
var utils = require('./utils') 
// Load webpack
var webpack = require('webpack') 
// Load webpack configuration merge tool
var merge = require('webpack-merge') 
// Load webpack.base.conf.js
var baseWebpackConfig = require('./webpack.base.conf') 
// A web pack extension that extracts code and separates it from files
// If we want to package webpack as a file css js, we need this plug-in
var ExtractTextPlugin = require('extract-text-webpack-plugin')
// A plug-in that can insert html and create a new. html file
var HtmlWebpackPlugin = require('html-webpack-plugin')
var env = config.build.env
// Merge webpack.base.conf.js
var webpackConfig = merge(baseWebpackConfig, {
  module: {
    // loader used
    loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true })
  },
  // Whether to use the "source map" development tool? For more information, please refer to previous articles of DDFE
  devtool: config.build.productionSourceMap ? '#source-map' : false,
  output: {
    // Compile output directory
    path: config.build.assetsRoot,
    // Compile output filename
    // We can add: 6 after the hash to decide how many hash values to use
    filename: utils.assetsPath('js/[name].[chunkhash].js'), 
    // Filename of a file output without an output name specified
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  vue: {
    // loader used when compiling. vue files
    loaders: utils.cssLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true
    })
  },
  plugins: [
    // Plug in used
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    // definePlugin receives strings and inserts them into the code, so you can write JS strings if you want
    new webpack.DefinePlugin({
      'process.env': env
    }),
    // Compress js (you can also compress css)
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),
    new webpack.optimize.OccurrenceOrderPlugin(),
    // extract css into its own file
    // Separate the css file
    new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
    // generate dist index.html with correct asset hash for caching.
    // you can customize output by editing /index.html
    // see https://github.com/ampedandwired/html-webpack-plugin
    // Input and output. html file
    new HtmlWebpackPlugin({
      filename: config.build.index,
      template: 'index.html',
      // Whether to inject html
      inject: true, 
      // How to compress
      minify: { 
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
      chunksSortMode: 'dependency'
    }),
    // split vendor js into its own file
    // Static filename of a file output without an output filename specified
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: function (module, count) {
        // 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
        )
      }
    }),
    // extract webpack runtime and module manifest to its own file in order to
    // prevent vendor hash from being updated whenever app bundle is updated
    // Static filename for file output without output filename specified
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      chunks: ['vendor']
    })
  ]
})
// Use the following configuration with gzip on
if (config.build.productionGzip) {
  // Load compression webpack plugin
  var CompressionWebpackPlugin = require('compression-webpack-plugin')
  // Add the following plug-in to webpackconfig.plugins
  webpackConfig.plugins.push(
    // Compression using the compression webpack plugin plug-in
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}
module.exports = webpackConfig

Development and packaging instruction configuration

As shown in the figure below, there is only client code in the project, so you only need to distinguish different packaging environments. When you package jenkins in the background, you only need to execute the corresponding instructions, and the front end does not need any modification

dev-server.js

//Call different web pack packing files with different parameters after packing instruction
var webpackConfig = process.env.NODE_ENV === 'testing'
  ? require('./webpack.prod.conf')
  : require('./webpack.dev.conf')
//When the path parameter of the server cannot be used to load the agent, or more flexibility is required, the path mode of the agent is context
// var context = config.dev.context
// var proxypath = config.dev.proxypath
//
// var options = {
//   target: proxypath,
//   changeOrigin: true,
// }
// if (context.length) {
//   app.use(proxyMiddleware(context, options))
// }

config/index.js

proxyTable: {//Reverse proxy settings (for development environments only) are aware of a single interface
      '/api': {
        // target: 'http://com', / / production-
        target: 'http://com', // Testing-
        changeOrigin: true,
        // pathRewrite: {
        //   '^/api': '/api/4'
        // }
      },
    },
    // context: [/ / determine which requests should be proxied to the target host
    //   '/api'
    // ],
    // proxypath: 'http://com', / / target host to agent

dev.env.js

var merge = require('webpack-merge')
var prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  BASE_API: '"test api Interface address"',
  APP_ORIGIN: '"test api Interface address"',
  WX_APP_ID: '"Test wechat ID"'
})

prod.env.js

module.exports = {
  NODE_ENV: '"production"',
  BASE_API: '"formal api Interface address"',
  APP_ORIGIN: '"formal api Interface address"',
  WX_APP_ID: '"formal api Interface address"'
}

webpack.base.config.js

Path shorthand, file pathname can be called directly in the component
resolve: {
    extensions: ['.js', '.vue', '.json'],
    // fallback: [path.join(__dirname, '../node_modules')],
    alias: {
      'src': path.resolve(__dirname, '../src'),
      'api': path.resolve(__dirname, '../src/api'),
      'assets': path.resolve(__dirname, '../src/assets'),
      'components': path.resolve(__dirname, '../src/components'),
      'views': path.resolve(__dirname, '../src/views'),
      'config': path.resolve(__dirname, '../src/config'),
      'util': path.resolve(__dirname, '../src/util'),
      'store': path.resolve(__dirname, '../src/store'),
      'shared': path.resolve(__dirname, '../src/shared'),
      'static': path.resolve(__dirname, '../static'),
      'router': path.resolve(__dirname, '../src/router'),
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },
//Global dependent third party js Plug in, not recommended to write here, increased package file size
  plugins:[
    new webpack.ProvidePlugin({
      // $: "jquery",
      // jQuery: "jquery",
      // "window.jQuery": "jquery",
      //"_": "lodash"
    })
  ],

package.json

//Package file directive is configured here
 "scripts": {
    "dev": "node build/dev-server.js",
    "build:prod": "cross-env NODE_ENV=production node build/build.js",
    "build:dev": "cross-env NODE_ENV=development npm_config_preview=true  npm_config_report=true node build/build.js",
    "build:test": "cross-env NODE_ENV=testing node build/build.js",
    "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
    "e2e": "node test/e2e/runner.js",
    "test": "npm run unit && npm run e2e"
  },

build.js

Analysis of the size dependence of package files(Only under the test environment)
if(process.env.npm_config_preview){
      server.start({
        port: 9528,
        directory: './dist',
        file: '/index.html'
      });
      console.log('> Listening at ' +  'http://localhost:9528' + '\n')
    }

rem configuration

Import Taobao flexible.js, and remove the meta here

 <!--<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">-->

Baidu statistics, error log statistics

    <script>
      var _hmt = _hmt || [];
      (function() {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?0b0ad90031dfa360e567dde3fbfa6e49";
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(hm, s);
      })();
    </script>

After that is the introduction of mintUI and the configuration of vuex. It's the same. There are many online tutorials that won't be covered in detail, but we can recommend our router and the configuration of vuex. If you are interested, you can look at them,
For ajax encapsulation, please refer to my previous blog Encapsulate fetch method and call based on axios

router

Route splitting
// Route injection of customer and management end
import clientCfg from './router_client'
import managerCfg from './router_manager'
let routes = [
  {path: '/proxy',name:'proxy',component:proxy},
  {path: '/notFound',name:'notFound',component:notFound}
]

routes = new Set([...routes, ...managerCfg, ...clientCfg])

router_client.js

//It's just the lazy loading of routes
const login = resolve => require(['views/client/login'], resolve)
export default [
  //Jump to home page when address is empty
  {
    path: '/',
    redirect: '/client/login'
    // redirect: '/client/demand/list'
  }, {
    path: '/client/login',
    name:'login',
    meta: {pageTitle: 'Current page title'},
    component: login,
    beforeEnter(to, from, next) {
      if(Storage.get("clientUserToken")) {

      }
      next();
    }
  }

]

vuex

//Overriding mutaion method to implement object mode call
// actions
const actions = {
  // Administrator mobile number synchronization
  [loginUd.A.GET_PHONE]({commit, state}, phone) {
    commit(loginUd.GET_PHONE,phone)
  },
  // Administrator verification code synchronization
  [loginUd.A.GET_CAPTCHA]({commit, state}, captcha) {
    commit(loginUd.GET_CAPTCHA, captcha)
  },
  // User login
  [loginUd.A.QUICK_LOGIN]({commit, state}, userInfo = {}) {
    return new Promise(async (resolve, reject) => {
      try {
        const userData = await callAuthLogin(userInfo.userPhone,userInfo.captcha);
        console.log(userData)
        if(!isEmptyObject(userData) && !!userData.user.userToken) {
          Storage.set("clientUserToken", userData.user.userToken)
          commit(loginUd.GET_USER_INFO,userData.user)
          router.push({name: 'home'})
        }
        resolve();
      }catch (error){
        reject(error);
      }
    })
  },
  // Get verification code
  [loginUd.A.GET_AUTH_CODE]({commit, state}, phone) {
    return new Promise(async (resolve, reject) => {
      try {
        const phoneData = callAuthCode(phone);
        if(phoneData) {
          resolve();
        }
      }catch (error) {
        reject(error);
      }
    })
  }
}

// mutations
const mutations = {
  [loginUd.GET_PHONE](state, phone) {
    state.c_loginDetail.userPhone = phone;
  },
  [loginUd.GET_CAPTCHA](state, captcha) {
    state.c_loginDetail.captcha = captcha;
  },
  [loginUd.GET_USER_INFO](state, userInfo) {
    state.c_loginDetail.user = userInfo;
  }
}

types.js

// Client
export const loginUd = createModules('loginUd', {
  A: ['GET_PHONE', 'GET_CAPTCHA', 'QUICK_LOGIN', 'GET_AUTH_CODE'],
  M: ['GET_PHONE', 'GET_CAPTCHA', 'GET_USER_INFO']
})

Keywords: Webpack Vue npm github

Added by Gabriel_Haukness on Mon, 04 May 2020 06:37:28 +0300