1、webpack.devServer配置
如果要启用webpack代理,则配置如下:

  webpackConfig.devServer = {
    hot: true,
    hotOnly: false, // If true: Enable HMR without page refresh when build failure
    open: true, // auto open 
    port: 8080,
    overlay: true,
    publicPath: '/',
    proxy: {
      '(/crmp/admin/**)': { 
        target    : 'http://10.6.183.146:8088/admin', 
        changeOrigin: true,
        secure: false, // 接受运行在 HTTPS 上,且使用了无效证书的后端服务器
      },
    },
    quiet: true,
  }

proxy中的代理请求地址必须是相对路径,且从相对路径左侧开始匹配,如 (/crmp/admin/xxx)代理的是 http://localhost:8080/crmp/admin/xxx请求路径,代理到后端http://10.6.183.146:8088/admin/xxx地址。
代理后,我们在浏览器网络栏看到的请求地址是:
Request URL:http://localhost:8080/admin/xxx。
实际是代理服务器请求了后端接口,再把数据响应给localhost:8080。

2、搭建基于webpack4+vue2.6搭建swig模板渲染多页应用骨架
(1)项目结构
屏幕快照 2020-06-22 下午2.16.33.png

(2)webpack.config.js配置文件:

var path = require('path')
var utils = require('./utils')
const webpack = require('webpack')
const ENV_CONFIG = require('./config')
const vueLoaderConfig = require('./vue-loader.conf')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const CopyPlugin = require('copy-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const AddAssetPlugin = require('add-asset-html-webpack-plugin')
const env = process.env.NODE_ENV
const isDev = env === 'dev'

var entries = utils.getEntry('./src/views/**/main.js') // 获得入口 js 文件
var pages = utils.getHtmlConfig(false) // 获得多页面 HTML 模板文件
function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

var webpackConfig = {
  mode: isDev ? 'development' : 'production',
  entry: entries,
  output: {
    path: ENV_CONFIG[env].assetsRoot,
    publicPath: ENV_CONFIG[env].assetsPublicPath,
    filename: isDev ? '[name].js' : 'js/[name].js?v=[contenthash:7]',
    chunkFilename: isDev ? '[name].chunk.js' : 'js/[name].chunk.js?v=[contenthash:7]'
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
        'vue$': 'vue/dist/vue.esm.js',
        'src': resolve('src'),
        'assets': resolve('src/assets'),
        'utils': resolve('src/utils'),
        'api': resolve('src/api'),
        'views': resolve('src/views'),
        'plugins': resolve('src/plugins'),
        'components': resolve('src/components')
    }
  },
  resolveLoader: {
    modules: [resolve('node_modules'), resolve('loaders')]
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader?cacheDirectory',
        exclude: /node_modules/,
        include: resolve('src'),
        exclude: resolve('src/assets/js/swiper.min.js'),
        options: {
          presets: ['@babel/preset-env']
        }
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig,
        include: [resolve('src/views'), resolve('src/components'), resolve('node_modules/@pa/widget-loader/src/')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 1000,
          publicPath: ENV_CONFIG[env].assetsPublicPath,
          name: 'images/[name].[ext]?v=[contenthash:7]'
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 1000,
          publicPath: ENV_CONFIG[env].assetsPublicPath,
          name: 'fonts/[name].[ext]?v=[contenthash:7]'
        }
      }
    ].concat(
      utils.styleLoaders({ sourceMap: ENV_CONFIG[env].cssSourceMap, extract: !isDev })
    )
  },
  devtool: isDev ? 'cheap-module-eval-source-map' : 'cheap-module-source-map',
  plugins: [
    ...pages.map(page => new HtmlWebpackPlugin(page)),
    new VueLoaderPlugin(),
    new AddAssetPlugin({
      filepath: resolve('dll/vendor.dll.js')
    }),
    new webpack.DllReferencePlugin({
      manifest: resolve('dll/vendor.manifest.json')
    }),
    // new AddAssetPlugin({
    //   filepath: resolve('dll/patool.dll.js')
    // }),
    // new webpack.DllReferencePlugin({
    //   manifest: resolve('dll/patool.manifest.json')
    // }),
    new webpack.DefinePlugin({
      NODE_ENV: JSON.stringify(env),
      PUBLIC_PATH: JSON.stringify(ENV_CONFIG[env].assetsPublicPath),
      'process.env':JSON.stringify({
        MOCK_ENV: isDev && ENV_CONFIG.dev.enableRapMock && 'MOCK',
      })
    }),
    new CopyPlugin([
       { from: resolve('src/assets/static'), to: 'static', ignore: ['.*'] },
       { from: resolve('src/assets/static'), to: 'static', ignore: ['.*'] }
    ])
  ],
  optimization: {
    minimize: true,
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
            name: 'vendors', //若默认名称将根据入口实际共用生成多个组,按需引入;设置名称只生成一个公共组,全部页面引入
            test: /[\\/]node_modules[\\/]/,
            priority: -10,
            minChunks: 1 //wait 3?
        },
        default: { //显示配置default分组才能覆盖默认default分组(minChunks: 2)
            name: 'common', // 若默认名称将根据入口实际共用生成多个组,根据实际引入;设置名称只生成一个公共组,全部页面引入
            minSize: 100, //大小超过100个字节
            minChunks: 3, //最少引入了3次
            priority: -20,
            reuseExistingChunk: true
        }
      }
    },
    runtimeChunk: false
  }
}

if (isDev) {
  webpackConfig.devServer = {
    hot: true,
    hotOnly: false, // If true: Enable HMR without page refresh when build failure
    open: ENV_CONFIG.dev.open,
    port: ENV_CONFIG.dev.port,
    overlay: true,
    publicPath: ENV_CONFIG.dev.assetsPublicPath,
    proxy: ENV_CONFIG.dev.proxyTable,
    quiet: true,
  }
} else {
  webpackConfig.plugins.push(new MiniCssExtractPlugin({
    filename: 'css/[name].css?v=[contenthash:7]',
    chunkFilename: 'css/[id].css?v=[contenthash:7]'
  }))
  webpackConfig.plugins.push(new OptimizeCssAssetsPlugin())
}

module.exports = webpackConfig

(3)webpack.dll.config.js配置文件:

const path = require('path')
const webpack = require('webpack')

module.exports = {
 mode: 'production',
 entry: {
   vendor: [
     'vue/dist/vue.esm.js',
     'babel-polyfill'
   ],
   // patool: [
   //   '@pa/poppy-ui'
   // ]
 },
 output: {
   path: path.join(__dirname, '../dll'),
   filename: '[name].dll.js',
   library: '[name]_library'
 },
 devtool:  'cheap-module-source-map',
 plugins: [
   new webpack.DllPlugin({
     name: '[name]_library',
     path: path.join(__dirname, '../dll', '[name].manifest.json')
   })
 ]
}

(4)src/plugins/index.js文件:

import mixins from './mixins'
import * as filters from './filters'
import * as directives from './directives'

let COMPOSE_PLUGIN = {}
COMPOSE_PLUGIN.install = function(Vue, options) {
  // global directives
  Object.keys(directives).forEach(key => {
    Vue.directive(key, directives[key])
  })

  // global filters
  Object.keys(filters).forEach(key => {
      Vue.filter(key, filters[key])
  })

  // global mixins
  Vue.mixin(mixins)
}

export default COMPOSE_PLUGIN

(4)统一请求封装Service.js文件:


function Service() {
  this.request = function(opts) {
    this.opts = Object.assign({}, Service.DEFAULTS, opts)

    if (window.dingding && dingding.http && dingding.http.request) {
      return this._dingdingRequest()
    } else if (window.$ && $.ajax) {
      return this._ajaxRequest()
    } else { // 原生请求
      return this._xhrRequest()
    }
  }

  this.get = function(opts) {
    return this.request((opts.method = 'GET') && opts)
  }

  this.post = function(opts) {
    return this.request((opts.method = 'POST') && opts)
  }
}

Service.DEFAULTS = {
  url: '',
  method: 'GET',
  contentType: 'application/x-www-form-urlencoded;charset=utf-8',
  dataType: 'json', // 期待返回数据类型
  timeout: 15, // 请求超时设置
  data: {}
}

// dingding request
Service.prototype._dingdingRequest = function () {
  console.log('请求工具-->', 'dingding');
  const config = {
    url: this.opts.url,
    method: this.opts.method,
    headers: {
      'Content-Type': this.opts.contentType
    },
    timeout: this.opts.timeout,
    xhrFields: {
      withCredentials: true
    }
  }
  if (config.method.toUpperCase() === 'POST') {
    config.body = this.opts.data
  } else {
    config.qs = this.opts.data
  }

  return new Promise(function(resolve, reject) {
    dingding.http.request(config, function (error, res) {
      if (error) { // 调用失败
        // dingding.toast.show({
        //   message: '系统异常,请稍后再试!'
        // })
        reject('AJAX_ERROR')
        return
      }
      if (res.status === 200) {
        isJsonStr(res.body) ? resolve(JSON.parse(res.body)) : resolve(res.body) // res.body 响应原始内容
      } else {
        reject(res.status + res.statusText) // 响应状态码 + 状态码对应的文本(如 200 -- OK)
      }
    })
  })
}

// ajax request
Service.prototype._ajaxRequest = function () {
  console.log('请求工具-->', '$.ajax');
  let config = {
    url: this.opts.url,
    type: this.opts.method,
    data: this.opts.data, // get请求时,会自动序列化参数后添加到url末尾
    headers: {
      'Content-Type': this.opts.contentType
    },
    dataType: this.opts.dataType,
    timeout: this.opts.timeout,
    xhrFields: {
      withCredentials: true
    }
  }

  return new Promise(function(resolve, reject) {
    // when request succeeds
    config.success = function(data, status, xhr) {
      if (xhr.status === 200) {
        resolve(data) // data: 响应原始内容
      } else {
        reject(xhr.status + xhr.statusText) // 响应状态码 + 状态码对应的文本(如 200 -- OK)
      }
    }
    //  if there is an error (timeout, parse error, or status code not in HTTP 2xx)
    config.error = function(xhr, textStatus) {
      reject('AJAX_ERROR')
    }
    $.ajax(config)
  })
}

// XMLHttpRequest request
Service.prototype._xhrRequest = function () {
  console.log('请求工具-->', 'XMLHttpRequest');
  let url = this.opts.url
  const method = this.opts.method.toUpperCase()
  const data = this.opts.data
  // 如果是 GET 请求,需要处理 data 里的数据并且以一定的规则拼接到 url 后
  if (method === 'GET') {
    var formData = []
    for(key in this.opts.data) {
      formData.push(''.concat(key, '=', this.opts.data[key]))
    }
    let params = formData.join('&')
    if (params.length > 0) {
      url += url.indexOf('?') >= 0 ? ''.concat('&', params) : ''.concat('?', params)
    }
  }

  let xhr = null
  if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest()
  } else if (window.ActiveXObject) {
    xhr=new ActiveXObject("Microsoft.XMLHTTP")
  }
  xhr.timeout = this.opts.timeout
  // xhr.responseType =  'json' // 指定类型与服务器返回值类型若是兼容的,则按指定类型返回,若不兼容则返回null
  xhr.withCredentials = true
  xhr.open(method, url, true)
  xhr.setRequestHeader('Content-Type', this.opts.contentType)

  return new Promise(function(resolve, reject) {
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          isJsonStr(xhr.response) ? resolve(JSON.parse(xhr.response)) : resolve(xhr.response) // xhr.response,整个响应体
        } else {
          reject(xhr.status + xhr.statusTxt) // 响应状态码 + 状态码对应的文本(如 200 -- OK)
        }
      }
    }
    xhr.onerror = function(e) {
      reject('AJAX_ERROR')
    }
    xhr.ontimeout = function(e) { }
    xhr.send(method === 'POST' ? data : null)
  })
}

// 检查是否JSON文本
const isJsonStr = function(value){
  try {
    eval('('+value+')')
    return true
  } catch(er){
    return false
  }
}

// module.exports = {
//  service: new Service()
// }


var req = new Service()

// var p = req.request({
//   url: '/hget',
//   method: 'GET',
//   data: { name: 'zhangsan' }
// })

var p = req.request({
  url: '/hput',
  method: 'PUT',
  data: { name: 'zhangsan' }
})

// var p = req.request({
//   url: '/hpost',
//   method: 'POST',
//   data: { name: 'zhangsan' }
// })
p.then(res => console.log('resolved', res)).catch(err => console.log('rejected', err))

1、babel转译和babel-polyfill垫片
这里需要理解下三个概念:

  • 最新ES 语法:比如,箭头函数
  • 最新ES API:,比如,Promise
  • 最新ES 实例方法:比如,String.protorype.includes

@babel/preset-env默认支持语法转化,需要开启useBuiltIns配置才能转化API实例方法

此外,不管是写项目还是写Library/Module,使用@babel/preset-env并正确配置就行。多看英文原稿说明,中文总结看看就好,别太当真。
参考一:关于Babel你只需要知道三个插件
参考二:babel-preset-env使用指南
参考三:useBuiltIns


JohnsonGH
32 声望1 粉丝