详解vue2.0脚手架的webpack 配置文件分析


Posted in Javascript onMay 27, 2017

前言

作为 Vue 的使用者我们对于 vue-cli 都很熟悉,但是对它的 webpack 配置我们可能关注甚少,今天我们为大家带来 vue-cli#2.0 的 webpack 配置分析

vue-cli 的简介、安装我们不在这里赘述,对它还不熟悉的同学可以直接访问 vue-cli 查看

目录结构

.
├── README.md
├── build
│  ├── 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
├── config
│  ├── dev.env.js
│  ├── index.js
│  └── prod.env.js
├── index.html
├── package.json
├── src
│  ├── App.vue
│  ├── assets
│  │  └── logo.png
│  ├── components
│  │  └── Hello.vue
│  └── main.js
└── static

本篇文章的主要关注点在

build - 编译任务的代码

config - webpack 的配置文件

package.json - 项目的基本信息

入口

从 package.json 中我们可以看到

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

当我们执行 npm run dev / npm run build 时运行的是 node build/dev-server.js 或 node build/build.js

dev-server.js

让我们先从 build/dev-server.js 入手

// 检查 Node 和 npm 版本
require('./check-versions')()

// 获取 config/index.js 的默认配置
var config = require('../config')

// 如果 Node 的环境无法判断当前是 dev / product 环境
// 使用 config.dev.env.NODE_ENV 作为当前的环境

if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)

// 使用 NodeJS 自带的文件路径工具
var path = require('path')

// 使用 express
var express = require('express')

// 使用 webpack
var webpack = require('webpack')

// 一个可以强制打开浏览器并跳转到指定 url 的插件
var opn = require('opn')

// 使用 proxyTable
var proxyMiddleware = require('http-proxy-middleware')

// 使用 dev 环境的 webpack 配置
var webpackConfig = require('./webpack.dev.conf')

// default port where dev server listens for incoming traffic

// 如果没有指定运行端口,使用 config.dev.port 作为运行端口
var port = process.env.PORT || config.dev.port

// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware

// 使用 config.dev.proxyTable 的配置作为 proxyTable 的代理配置
var proxyTable = config.dev.proxyTable

// 使用 express 启动一个服务
var app = express()

// 启动 webpack 进行编译
var compiler = webpack(webpackConfig)

// 启动 webpack-dev-middleware,将 编译后的文件暂存到内存中
var devMiddleware = require('webpack-dev-middleware')(compiler, {
 publicPath: webpackConfig.output.publicPath,
 stats: {
  colors: true,
  chunks: false
 }
})

// 启动 webpack-hot-middleware,也就是我们常说的 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
// 将 proxyTable 中的请求配置挂在到启动的 express 服务上
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
// 使用 connect-history-api-fallback 匹配资源,如果不匹配就可以重定向到指定地址
app.use(require('connect-history-api-fallback')())

// serve webpack bundle output
// 将暂存到内存中的 webpack 编译后的文件挂在到 express 服务上
app.use(devMiddleware)

// enable hot-reload and state-preserving
// compilation error display
// 将 Hot-reload 挂在到 express 服务上
app.use(hotMiddleware)

// serve pure static assets
// 拼接 static 文件夹的静态资源路径
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
// 为静态资源提供响应服务
app.use(staticPath, express.static('./static'))

// 让我们这个 express 服务监听 port 的请求,并且将此服务作为 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 (process.env.NODE_ENV !== 'testing') {
  opn(uri)
 }
})

webpack.dev.conf.js

刚刚我们在 dev-server.js 中用到了 webpack.dev.conf.js 和 index.js,我们先来看一下 webpack.dev.conf.js

// 同样的使用了 config/index.js
var config = require('../config') 

// 使用 webpack
var webpack = require('webpack') 

// 使用 webpack 配置合并插件
var merge = require('webpack-merge') 

// 使用一些小工具
var utils = require('./utils') 

// 加载 webpack.base.conf
var baseWebpackConfig = require('./webpack.base.conf') 

// 使用 html-webpack-plugin 插件,这个插件可以帮我们自动生成 html 并且注入到 .html 文件中
var HtmlWebpackPlugin = require('html-webpack-plugin') 

// add hot-reload related code to entry chunks
// 将 Hol-reload 相对路径添加到 webpack.base.conf 的 对应 entry 前
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
 baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})

// 将我们 webpack.dev.conf.js 的配置和 webpack.base.conf.js 的配置合并
module.exports = merge(baseWebpackConfig, {
 module: {
  // 使用 styleLoaders
  loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
 },
 // eval-source-map is faster for development
 // 使用 #eval-source-map 模式作为开发工具,此配置可参考 DDFE 往期文章详细了解
 devtool: '#eval-source-map',
 plugins: [

  // definePlugin 接收字符串插入到代码当中, 所以你需要的话可以写上 JS 的字符串
  new webpack.DefinePlugin({
   'process.env': config.dev.env
  }),
  // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
  new webpack.optimize.OccurenceOrderPlugin(),

  // HotModule 插件在页面进行变更的时候只会重回对应的页面模块,不会重绘整个 html 文件
  new webpack.HotModuleReplacementPlugin(),

  // 使用了 NoErrorsPlugin 后页面中的报错不会阻塞,但是会在编译结束后报错
  new webpack.NoErrorsPlugin(),
  // https://github.com/ampedandwired/html-webpack-plugin

  // 将 index.html 作为入口,注入 html 代码后生成 index.html文件
  new HtmlWebpackPlugin({
   filename: 'index.html',
   template: 'index.html',
   inject: true
  })
 ]
})

webpack.base.conf.js

我们看到在 webpack.dev.conf.js 中又引入了 webpack.base.conf.js, 它看起来很重要的样子,所以我们只能在下一章来看看 config/index.js 了 (摊手)

// 使用 NodeJS 自带的文件路径插件
var path = require('path') 

// 引入 config/index.js
var config = require('../config') 

// 引入一些小工具
var utils = require('./utils') 

// 拼接我们的工作区路径为一个绝对路径
var projectRoot = path.resolve(__dirname, '../') 

// 将 NodeJS 环境作为我们的编译环境
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

// 是否在 dev 环境下开启 cssSourceMap ,在 config/index.js 中可配置
var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)

// 是否在 production 环境下开启 cssSourceMap ,在 config/index.js 中可配置
var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)

// 最终是否使用 cssSourceMap
var useCssSourceMap = cssSourceMapDev || cssSourceMapProd

module.exports = {
 entry: {
   // 编译文件入口
  app: './src/main.js' 
 },
 output: {
   // 编译输出的根路径
  path: config.build.assetsRoot, 
  // 正式发布环境下编译输出的发布路径
  publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, 
  // 编译输出的文件名
  filename: '[name].js' 
 },
 resolve: {
  // 自动补全的扩展名
  extensions: ['', '.js', '.vue'],
  // 不进行自动补全或处理的文件或者文件夹
  fallback: [path.join(__dirname, '../node_modules')],
  alias: {
  // 默认路径代理,例如 import Vue from 'vue',会自动到 '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: [
   // 预处理的文件及使用的 loader
   {
    test: /\.vue$/,
    loader: 'eslint',
    include: projectRoot,
    exclude: /node_modules/
   },
   {
    test: /\.js$/,
    loader: 'eslint',
    include: projectRoot,
    exclude: /node_modules/
   }
  ],
  loaders: [
   // 需要处理的文件及使用的 loader
   {
    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 代码检查配置工具
  formatter: require('eslint-friendly-formatter')
 },
 vue: {
  // .vue 文件配置 loader 及工具 (autoprefixer)
  loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }),
  postcss: [
   require('autoprefixer')({
    browsers: ['last 2 versions']
   })
  ]
 }
}

config/index.js

终于分析完了 webpack.base.conf.js,来让我们看一下 config/index.js

index.js 中有 dev 和 production 两种环境的配置

// see http://vuejs-templates.github.io/webpack for documentation.
// 不再重复介绍了 ...
var path = require('path')

module.exports = {
 // production 环境
 build: { 
   // 使用 config/prod.env.js 中定义的编译环境
  env: require('./prod.env'), 
  index: path.resolve(__dirname, '../dist/index.html'), // 编译输入的 index.html 文件
  // 编译输出的静态资源根路径
  assetsRoot: path.resolve(__dirname, '../dist'), 
  // 编译输出的二级目录
  assetsSubDirectory: 'static', 
  // 编译发布上线路径的根目录,可配置为资源服务器域名或 CDN 域名
  assetsPublicPath: '/', 
  // 是否开启 cssSourceMap
  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
  // 是否开启 gzip
  productionGzip: false, 
  // 需要使用 gzip 压缩的文件扩展名
  productionGzipExtensions: ['js', 'css'] 
 },
 // dev 环境
 dev: { 
   // 使用 config/dev.env.js 中定义的编译环境
  env: require('./dev.env'), 
  // 运行测试页面的端口
  port: 8080, 
  // 编译输出的二级目录
  assetsSubDirectory: 'static', 
  // 编译发布上线路径的根目录,可配置为资源服务器域名或 CDN 域名
  assetsPublicPath: '/', 
  // 需要 proxyTable 代理的接口(可跨域)
  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.
  // 是否开启 cssSourceMap
  cssSourceMap: false 
 }
}

至此,我们的 npm run dev 命令就讲解完毕,下面让我们来看一看执行 npm run build 命令时发生了什么 ~

build.js

// https://github.com/shelljs/shelljs

// 检查 Node 和 npm 版本
require('./check-versions')() 

// 使用了 shelljs 插件,可以让我们在 node 环境的 js 中使用 shell
require('shelljs/global') 
env.NODE_ENV = 'production'

// 不再赘述
var path = require('path') 

// 加载 config.js
var config = require('../config') 

// 一个很好看的 loading 插件
var ora = require('ora') 

// 加载 webpack
var webpack = require('webpack') 

// 加载 webpack.prod.conf
var webpackConfig = require('./webpack.prod.conf') 

// 输出提示信息 ~ 提示用户请在 http 服务下查看本页面,否则为空白页
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'
)

// 使用 ora 打印出 loading + log
var spinner = ora('building for production...') 
// 开始 loading 动画
spinner.start() 

// 拼接编译输出文件路径
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
// 删除这个文件夹 (递归删除)
rm('-rf', assetsPath)
// 创建此文件夹 
mkdir('-p', assetsPath)
// 复制 static 文件夹到我们的编译输出目录
cp('-R', 'static/*', assetsPath)

// 开始 webpack 的编译
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')
})

webpack.prod.conf.js

// 不再赘述
var path = require('path')

// 加载 confi.index.js
var config = require('../config')

// 使用一些小工具
var utils = require('./utils') 

// 加载 webpack
var webpack = require('webpack') 

// 加载 webpack 配置合并工具
var merge = require('webpack-merge') 

// 加载 webpack.base.conf.js
var baseWebpackConfig = require('./webpack.base.conf') 

// 一个 webpack 扩展,可以提取一些代码并且将它们和文件分离开
// 如果我们想将 webpack 打包成一个文件 css js 分离开,那我们需要这个插件
var ExtractTextPlugin = require('extract-text-webpack-plugin')

// 一个可以插入 html 并且创建新的 .html 文件的插件
var HtmlWebpackPlugin = require('html-webpack-plugin')
var env = config.build.env

// 合并 webpack.base.conf.js
var webpackConfig = merge(baseWebpackConfig, {
 module: {
  // 使用的 loader
  loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true })
 },
 // 是否使用 #source-map 开发工具,更多信息可以查看 DDFE 往期文章
 devtool: config.build.productionSourceMap ? '#source-map' : false,
 output: {
  // 编译输出目录
  path: config.build.assetsRoot,
  // 编译输出文件名
  // 我们可以在 hash 后加 :6 决定使用几位 hash 值
  filename: utils.assetsPath('js/[name].[chunkhash].js'), 
  // 没有指定输出名的文件输出的文件名
  chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
 },
 vue: {
  // 编译 .vue 文件时使用的 loader
  loaders: utils.cssLoaders({
   sourceMap: config.build.productionSourceMap,
   extract: true
  })
 },
 plugins: [
  // 使用的插件
  // http://vuejs.github.io/vue-loader/en/workflow/production.html
  // definePlugin 接收字符串插入到代码当中, 所以你需要的话可以写上 JS 的字符串
  new webpack.DefinePlugin({
   'process.env': env
  }),
  // 压缩 js (同样可以压缩 css)
  new webpack.optimize.UglifyJsPlugin({
   compress: {
    warnings: false
   }
  }),
  new webpack.optimize.OccurrenceOrderPlugin(),
  // extract css into its own file
  // 将 css 文件分离出来
  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
  // 输入输出的 .html 文件
  new HtmlWebpackPlugin({
   filename: config.build.index,
   template: 'index.html',
   // 是否注入 html
   inject: true, 
   // 压缩的方式
   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
  // 没有指定输出文件名的文件输出的静态文件名
  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
  // 没有指定输出文件名的文件输出的静态文件名
  new webpack.optimize.CommonsChunkPlugin({
   name: 'manifest',
   chunks: ['vendor']
  })
 ]
})

// 开启 gzip 的情况下使用下方的配置
if (config.build.productionGzip) {
 // 加载 compression-webpack-plugin 插件
 var CompressionWebpackPlugin = require('compression-webpack-plugin')
 // 向webpackconfig.plugins中加入下方的插件
 var reProductionGzipExtensions = '\\.(' + config.build.productionGzipExtensions.join('|') + '$)'
 webpackConfig.plugins.push(
  // 使用 compression-webpack-plugin 插件进行压缩
  new CompressionWebpackPlugin({
   asset: '[path].gz[query]',
   algorithm: 'gzip',
   test: new RegExp(reProductionGzipExtensions), // 注:此处因有代码格式化的bug,与源码有差异
   threshold: 10240,
   minRatio: 0.8
  })
 )
}

module.exports = webpackConfig

总结

 vue2.0脚手架的webpack 配置文件分析借此回顾下,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
提升你网站水平的jQuery插件集合推荐
Apr 19 Javascript
Jquery实现的角色左右选择特效
May 21 Javascript
easyui Droppable组件实现放置特效
Aug 19 Javascript
jQuery ui实现动感的圆角渐变网站导航菜单效果代码
Aug 26 Javascript
基于jQuery日历插件制作日历
Mar 11 Javascript
JS只能输入正整数的简单实例
Oct 07 Javascript
js前端解决跨域问题的8种方案(最新最全)
Nov 18 Javascript
360提示[高危]使用存在漏洞的JQuery版本的解决方法
Oct 27 jQuery
通过一个简单的例子学会vuex与模块化
Nov 22 Javascript
vue.js仿hover效果的实现方法示例
Jan 28 Javascript
js中的this的指向问题详解
Aug 29 Javascript
JS array数组检测方式解析
May 19 Javascript
详解如何使用webpack打包Vue工程
May 27 #Javascript
Jquery中attr与prop的区别详解
May 27 #jQuery
angular使用post、get向后台传参的问题实例
May 27 #Javascript
AngularJS中使用ngModal模态框实例
May 27 #Javascript
angularJS模态框$modal实例代码
May 27 #Javascript
AngularJS入门教程二:在路由中传递参数的方法分析
May 27 #Javascript
AngularJS入门教程一:路由用法初探
May 27 #Javascript
You might like
怎样给PHP源代码加密?PHP二进制加密与解密的解决办法
2013/04/22 PHP
合格的PHP程序员必备技能
2015/11/13 PHP
什么是PHP7中的孤儿进程与僵尸进程
2019/04/14 PHP
jquery的颜色选择插件实例代码
2008/10/02 Javascript
JQuery 初体验(建议学习jquery)
2009/04/25 Javascript
JavaScript 学习笔记(十一)
2010/01/19 Javascript
ExtJs扩展之GroupPropertyGrid代码
2010/03/05 Javascript
js中scrollHeight,scrollWidth,scrollLeft,scrolltop等差别介绍
2012/05/16 Javascript
JavaScript NodeTree导航栏(菜单项JSON类型/自制)
2013/02/01 Javascript
jquery sortable的拖动方法示例详解
2014/01/16 Javascript
调用jQuery滑出效果时闪烁的解决方法
2014/03/27 Javascript
分享2个jQuery插件--jquery.fileupload与artdialog
2014/12/26 Javascript
js库Modernizr的介绍和使用
2015/05/07 Javascript
使用Vue.js开发微信小程序开源框架mpvue解析
2018/03/20 Javascript
LayUi使用switch开关,动态的去控制它是否被启用的方法
2019/09/21 Javascript
vue-cli和v-charts实现可视化图表过程解析
2019/10/08 Javascript
[01:13:59]LGD vs Mineski Supermajor 胜者组 BO3 第三场 6.5
2018/06/06 DOTA
Python字符串和文件操作常用函数分析
2015/04/08 Python
Python实现读取并保存文件的类
2017/05/11 Python
python版本坑:md5例子(python2与python3中md5区别)
2017/06/20 Python
Python实现的批量修改文件后缀名操作示例
2018/12/07 Python
pyecharts在数据可视化中的应用详解
2020/06/08 Python
matplotlib 生成的图像中无法显示中文字符的解决方法
2020/06/10 Python
国际书籍零售商:Wordery
2017/11/01 全球购物
泰国办公用品购物网站:OfficeMate
2018/02/04 全球购物
澳大利亚婴儿、幼儿和儿童在线设计师商店:Smooch Baby
2019/02/16 全球购物
Crabtree & Evelyn欧盟:豪华洗浴、身体和护发
2021/03/09 全球购物
俄罗斯极限运动网上商店:Board Shop №1
2020/12/18 全球购物
拾金不昧的表扬信
2014/01/16 职场文书
老公保证书范文
2014/04/29 职场文书
室内设计专业毕业生求职信
2014/05/02 职场文书
大学生推广普通话演讲稿
2014/09/21 职场文书
法院个人总结
2015/03/03 职场文书
付款证明模板
2015/06/19 职场文书
护士心得体会范文
2016/01/25 职场文书
matplotlib画混淆矩阵与正确率曲线的实例代码
2021/06/01 Python