基于 vue-cli 的 webpack 打包之第三方库优化

由于加入了数个较为庞大的库,导致vendor.js 达到了恐怖的8M, 为此参考了网上的不少优化资料,并查阅官方文档进行一定的修正,得出一个较为满意的方案。
首先优化方案有两种 CDN和 DLL。

CDN 方案

该非常直接,粘贴到index.html模板中, 在build/webpack.base.conf.js 加上externals: { 'jquery': 'Jquery' }, 使用时和正常安装的没有区别, webpack打包时并不会引入该包。
CDN方案具体就不多介绍,因为这个方案在深思熟虑后被放弃了,因为别人的 CND无法放心,可能会极小可能访问出现问题,而且包过多时请求数暴涨会引起页面加载的缓慢。

DLL 方案

此方案属于对vendor.js文件打包的再次优化,有目标的合并较小的包,较大的包单独存在。
执行此方案前需要知道哪些包占了vendor.js文件较大的位置,可以独立出来,此时可以在package.json加上新的运行命令(使用了vue-cli内置的webpack-bundle-analyzer)

"build:sit-preview": "NODE_ENV=production env_config=sit npm_config_preview=true npm_config_report=true node build/build.js"

通过webpack-bundle-analyzer我们可以观察到包的组成情况dll的配置
 
接下来开始优化,在config/index.js文件中新增一个dll配置对象

   dll: {
    entry: {
      vue: [
        'vue/dist/vue.esm.js',
        'vue-router',
        'vuex'
      ],
      ui: [
        'element-ui'
      ],
      echarts: [
        'echarts'
      ],
      utils: [
        'axios',
        'lodash',
        'jquery',
        'moment'
      ],
      other: [
        'perfect-scrollbar'
      ]
    },
    outputPath: path.resolve(__dirname, '../dist/dll'),
    publicPath: '/dll/'
  }

 
新增一个build/webpack.dll.conf.js文件, 需要先安装webpack-manifest-plugin库支持,该库为dll文件生成了一个文件列表

const path = require('path')
const webpack = require('webpack')
const ManifestPlugin = require('webpack-manifest-plugin')
const config = require('../config').dll
const webpackConfig = {
  entry: config.entry,
  output: {
    path: config.outputPath,
    filename: '[name].dll.[chunkhash].js',
    library: '[name]_library',
    publicPath: config.publicPath
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin(),
    new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn|en-gb/),
    new webpack.DllPlugin({
      path: path.join(config.outputPath, '[name]-manifest.json'),
      libraryTarget: 'commonjs2',
      name: '[name]_library'
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),
    new ManifestPlugin({
      fileName: path.join(config.outputPath, 'manifest.dll.json')
    })
  ]
}
module.exports = webpackConfig

 
新增一个build/build-dll.js文件,其作用是运行前面配置好的打包dll文件

'use strict'
process.env.NODE_ENV = 'production'
const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config').dll
const webpackConfig = require('./webpack.dll.conf')
const spinner = ora('开始构建 dll...')
spinner.start()
rm(path.resolve(__dirname, config.outputPath), err => {
  if (err) throw err
  webpack(webpackConfig, function (err, stats) {
    spinner.stop()
    if (err) throw err
    process.stdout.write(stats.toString({
      colors: true,
      modules: false,
      children: false,
      chunks: false,
      chunkModules: false
    }) + '\n\n')
    if (stats.hasErrors()) {
      console.log(chalk.red('  构建发生了错误.\n'))
      process.exit(1)
    }
    console.log(chalk.cyan('  构建完成.\n'))
    console.log(chalk.yellow(
      '  提示:当第三方库升级或变动时,请重新构建dll .\n'
    ))
  })
})

 
此时只需要在package.json文件中增加一个script方法

 "build:dll": "node build/build-dll.js"

运行yarn run build:dll可以在dist/dll文件夹中看到打包的dll文件
接下来让build项目时引入dll文件,让其打包时不引入dll已经打包的库
build/utils.js中增加一个辅助方法, 用于快速获得dll文件的路径

exports.dllPath = function (_path) {
  const outputPath = config.dll.outputPath
  return path.posix.join(outputPath, _path)
}

 
接下来修改build/webpack.prod.conf.js文件

// 获得dll文件的列表
const manifestDll = require(utils.dllPath('manifest.dll.json'))

HtmlWebpackPlugin插件中加一行

dll: manifestDll,

DllReferencePlugin插件注入到webpack,由于有多个 dll 文件,采用了自执行的匿名循环新建插件实例,运用扩展运算符写入到webpackConfig

  ...(function () {
      let res = []
      let keys = Object.keys(config.dll.entry)
      keys.forEach(key => {
        res.push(new webpack.DllReferencePlugin({
          context: path.resolve(__dirname, '../'),
          manifest: require(utils.dllPath(`${key}-manifest.json`))
        }))
      });
      return res
    })()

 
最后只需要在index.html模板中添加dll打包文件

 <!-- dll files will be auto injected -->
 <% for (var name in htmlWebpackPlugin.options.dll) { %>
   <script type="text/javascript" src="<%= htmlWebpackPlugin.options.dll[name] %>"></script>
 <% } %>

 

在升级第三方库的情况下,dll文件可以以一直使用,不需要打包。
现在运行build速度飞快,webpack不用每次都去重复打包第三方库了

vue-cli 在 apache 服务下配置 gzip 压缩

修改webpack配置文件

config/index.js文件中配置productionGzip: true

添加apache配置

<IfModule mod_headers.c>
 # 服务器上 gzip 压缩的 css 文件是否存在
 # 检查客户端是否接受 gzip
 RewriteCond "%{HTTP:Accept-encoding}" "gzip"
 RewriteCond "%{REQUEST_FILENAME}\.gz" -s
 RewriteRule "^(.*)\.css" "$1\.css\.gz" [QSA]
 # 服务器上 gzip 压缩的 js 文件是否存在
 # 检查客户端是否接受 gzip
 RewriteCond "%{HTTP:Accept-encoding}" "gzip"
 RewriteCond "%{REQUEST_FILENAME}\.gz" -s
 RewriteRule "^(.*)\.js" "$1\.js\.gz" [QSA]
 # Serve correct content types, and prevent mod_deflate double gzip.
 RewriteRule "\.css\.gz$" "-" [T=text/css,E=no-gzip:1]
 RewriteRule "\.js\.gz$" "-" [T=text/javascript,E=no-gzip:1]
 <FilesMatch "(\.js\.gz|\.css\.gz)$">
 # 提供正确的编码类型
 Header append Content-Encoding gzip
 # Force proxies to cache gzipped &
 # non-gzipped css/js files separately.
 Header append Vary Accept-Encoding
 </FilesMatch>
</IfModule>

配置可以直接写在.htaccess文件中
apache 配置参考自 http://httpd.apache.org/docs/2.4/mod/mod_deflate.html

如何判断是否生效?

查看浏览器响应头是否包含Content-Encoding: gzip

javascript实现全角与半角字符的转换

知识点
通过半角字符与全角字符的比较(ASCII字符),我们可以发现,拥有全角与半角之分的ASCII字符范围:0x20~0x7E。
 

比如:
符号 半角 全角 相差
# 0x0023 0xFF03 0xFEE0
? 0x003F 0xFF1F 0xFEE0
空格 0x0020 0x03000 0x2FE0
除了空格外,其他的字符中,全角与半角均相差:0xFFE0
因此,在全角与半角的字符转换中,需要对空格特殊处理。

例如:
全角 = 半角 + 0xFEE0
半角 = 全角 – 0xFFE0

参考来源 @ 脚步之家  http://www.jb51.net/article/59560.htm
在线转角 @ http://tool.chinaz.com/fullhalf/

/**
 * 转全角字符
 */
function toDBC(str){
    var result = "";
    var len = str.length;
    for(var i=0;i<len;i++)
    {
        var cCode = str.charCodeAt(i);
        //全角与半角相差(除空格外):65248(十进制)
        cCode = (cCode>=0x0021 && cCode<=0x007E)?(cCode + 65248) : cCode;
        //处理空格
        cCode = (cCode==0x0020)?0x03000:cCode;
        result += String.fromCharCode(cCode);
    }
    return result;
}
 
/**
 * 转半角字符
 */
function toSBC(str){
    var result = "";
    var len = str.length;
    for(var i=0;i<len;i++)
    {
        var cCode = str.charCodeAt(i);
        //全角与半角相差(除空格外):65248(十进制)
        cCode = (cCode>=0xFF01 && cCode<=0xFF5E)?(cCode - 65248) : cCode;
        //处理空格
        cCode = (cCode==0x03000)?0x0020:cCode;
        result += String.fromCharCode(cCode);
    }
    return result;
}

AngularJS 关于 $http 服务 then catch finally 的使用

/**
 * AngularJS 关于 $http 服务 then catch finally 的使用
 * @author Anker 2016.11.23
 */
// AngularJS $http 服务参考文档 https://docs.angularjs.org/api/ng/service/$http
// AngularJS $q 服务参考文档 https://docs.angularjs.org/api/ng/service/$q
// 原生JS Promise 服务参考文档 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
// $http.get() 实现 $q 服务 https://www.peterbe.com/plog/promises-with-$http
app.controller('MainCtrl', function($scope, $http) {
    // $http().then(successCallback[,errorCallback][,notifyCallback]).catch().finally();
    $http({
        method: '', // get | post
        url: '',
        params : {}, // get
        data : {} // post
    })
    // 当请求成功时
    .then(function (successCallback) {
        // 响应主体处理代码
        (function (response) {
            console.log('Got successCallback: ',response);
        })(successCallback.data);
    })
    // 当请求错误时
    .catch(function (errorCallback) {
        // 响应主体处理代码
        (function (response) {
            console.log('Got errorCallback: ',response);
        })(errorCallback.data);
    })
    // 当请求完成时 不管结果如何
    .finally(function(callback, notifyCallback) {
        console.log('Got notification: ',callback);
    });
    // successCallback 以及 errorCallback 响应对象具有这些属性:
    // data – {string|Object} – 响应体.
    // status – {number} – 响应的HTTP状态码.
    // headers – {function([headerName])} – 获取 header 的方法.
    // config – {Object} – 用于生成请求的配置对象.
    // statusText – {string} – 响应的HTTP状态文本.
})