Webpack 2.x 在Vue2.x项目中的应用

webpack@2x

最近开发的项目里使用了 Webpack + Vue 全家桶技术栈,鉴于Webpack的版本迭代还在高速发展期,我使用了Webpack2.x的稳定版本2.2.0来作为脚手架的打包构建工具。

管理资源

加载CSS

style-loader css-loader less-loader extract-text-webpack-plugin

项目中使用了 less 作为css的预处理,并且使用 extract-text-webpack-plugin 插件抽取css到单独的文件中。

_.cssLoader = config.cssModules
  ? 'css-loader?-autoprefixer&modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
  : 'css-loader?-autoprefixer'

_.cssProcessors = [
  {
    loader: '',
    test: /\.css$/
  }, {
    loader: 'less-loader?sourceMap',
    test: /\.less$/
  }
]
// extract css in standalone css files
_.cssProcessors.forEach(processor => {
  base.module.loaders.push({
    test: processor.test,
    loader: ExtractTextPlugin.extract({
      use: [_.cssLoader],
      fallback: 'style-loader'
    })
  })
})

加载Javascript & ES6

babel-loader

{
  test: /\.js$/,
  loaders: ['babel-loader?cacheDirectory=true'],
  exclude: [/node_modules/]
},
{
  test: /\.es6$/,
  loaders: ['babel-loader?cacheDirectory=true']
}

加载图片 & 加载字体

file-loader

{
  test: /\.(svg|ico|jpg|png|gif|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/,
  loader: 'file-loader',
  query: {
    limit: 10000,
    name: 'static/media/[name].[hash:8].[ext]'
  }
}

加载Vue单页文件

vue-loader

{
  test: /\.vue$/,
  loaders: ['vue-loader']
}

管理输出

html自动绑定bundle

HtmlWebpackPlugin 用来在打包后根据模版自动重新生成html文件并注入bundle。

new HtmlWebpackPlugin({
  title: config.title,
  template: path.resolve(__dirname, 'index.html'),
  filename: 'page.html'
})

清理 /dist 文件夹

clean-webpack-plugin

new CleanWebpackPlugin(['dist'])

开发工具

使用 source map

为了更容易地追踪错误和警告,JavaScript 提供了 source map 功能,将编译后的代码映射回原始源代码

使用 webpack-dev-server

webpack-dev-server 提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)

启用 HMR

Vue中使用vue-loader实现 vue 组件的 HMR


Tree Shaking

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)

uglifyjs-webpack-plugin

new webpack.optimize.UglifyJsPlugin({
  // sourceMap: true,
  compress: {
    warnings: false,
    drop_debugger: true,
    drop_console: true
  },
  output: {
    comments: false
  }
}),

优化方案与思路

分析Webpack打包大小

webpack-bundle-analyzer

一款分析 bundle 内容的插件及 CLI 工具,以便捷的、交互式、可缩放的树状图形式展现给用户。

非常好用的工具,它可以将打包后的文件树按打包大小展示为可视化的直观图。轻松定位有哪些构建后引入的包占比比较大,我们可以分析这些引入的包是否多余。进而进行优化。

webpack-bundle-analyzer

代码压缩

uglifyjs-webpack-pluginwebpack-parallel-uglify-plugin

上面讲到过 uglifyjs-webpack-plugin 的配置

下面用到 webpack-parallel-uglify-plugin 插件,多进程进行代码压缩。从而减少构建时间。

new ParallelUglifyPlugin({
  cacheDir: '.cache/',
  uglifyJS: {
    output: {
      comments: false,
      beautify: false
    },
    compress: {
      warnings: false,
      drop_console: true
    }
  }
})

多进程构建

happypack 参考官网 happypack


缓存与增量构建

针对项目中主要使用的ES6与Vue.js,通过babel-loader进行编译。babel-loader 可以缓存处理过的模块,对于没有修改过的文件不会再重新编译,cacheDirectory 有着2倍以上的速度提升,这对于 rebuild 有着非常大的性能提升。

{
  test: /\.js$/,
  loaders: ['babel-loader?cacheDirectory=true'],
  exclude: [/node_modules/]
}

引用外部CDN

如果项目环境允许,我们可以把常用的类库,比如 lodash/moment 这些工具库使用外部CDN在运行时(runtime)引入到项目中。构建的时候减少对这些库的打包。

{
  externals: {
     lodash : {
      commonjs: "lodash",
      amd: "lodash",
      root: "_" // indicates global variable
    }
  }
}

这里 lodash 这个外部 library 可以在 AMDCommonJS 模块系统中通过 lodash 访问,但在全局变量形式下用 _ 访问

需要把CDN配置在 index.html

<script src="https://cdn.bootcss.com/lodash.js/4.17.4/lodash.min.js"></script>

提取公共模版

CommonsChunkPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。

new webpack.optimize.CommonsChunkPlugin({
  name: 'vendor',
  minChunks: ({ resource }) => {
  return resource &&
         /\.(js|css|es6)$/.test(resource) &&
         resource.indexOf('node_modules') !== -1
  }
})

分离第三方依赖

DllPlugin 这个插件是在一个额外的独立的 webpack 设置中创建一个只有 dll 的 bundle(dll-only-bundle)。 这个插件会生成一个名为 manifest.json 的文件,这个文件是用来让 DLLReferencePlugin 映射到相关的依赖上去的。我们这里生成的依赖文件为 vendor-manifest.json

var path = require('path')
var webpack = require('webpack')

module.exports = {
  entry: {
    vendor: [
      'axios',
      'echarts',
      'iview',
      'lodash',
      'moment',
      'q',
      'qs',
      'vue/dist/vue.common.js',
      'vuex',
      'vue-router',
      'vuex-router-sync'
    ]
  },
  output: {
    path: path.join(__dirname, '../static'),
    filename: '[name].dll.js',
    library: '[name]_library'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn|en-gb/),
    new webpack.DllPlugin({
      path: path.join(__dirname, '.', '[name]-manifest.json'),
      libraryTarget: 'commonjs2',
      name: '[name]_library'
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ],
  resolve: {
    modules: ['node_modules']
  }
}

DllReferencePlugin 这个插件是在 webpack 主配置文件中设置的, 这个插件把只有 dll 的 bundle(们)(dll-only-bundle(s)) 引用到需要的预编译的依赖。它通过分析上一步使用 DllPlugin 生成的 vendor-mainfest.json 来把名称映射到模块的id上,生产环境打包过程中就会略过通过 dll 打包的模块。从而减少打包体积,大幅度减少构建时间。

// 分离vendor
new webpack.DllReferencePlugin({
  context: path.resolve(__dirname, '..'),
  manifest: require('./vendor-manifest.json')
})

copy-webpack-plugin 将预先打包好的dll静态文件拷贝到 dist 目录

new CopyWebpackPlugin([
  {
    from: _.cwd('./static'),
    // to the roor of dist path
    to: './'
  }
])
comments powered by Disqus