详解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 相关文章推荐
javascript实现checkBox的全选,反选与赋值
Mar 12 Javascript
jQuery标签编辑插件Tagit使用指南
Apr 21 Javascript
jQuery实现的进度条效果
Jul 15 Javascript
Sea.JS知识总结
May 05 Javascript
详解js的延迟对象、跨域、模板引擎、弹出层、AJAX【附实例下载】
Dec 19 Javascript
原生JS实现幻灯片
Feb 22 Javascript
jquery中有哪些api jQuery主要API
Nov 20 jQuery
JS重学系列之聊聊new操作符
Mar 04 Javascript
JS中的算法与数据结构之链表(Linked-list)实例详解
Aug 20 Javascript
JavaScript this关键字指向常用情况解析
Sep 02 Javascript
用JavaScript实现贪吃蛇游戏
Oct 23 Javascript
Js实现粘贴上传图片的原理及示例
Dec 09 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 fgetcsv 定义和用法(附windows与linux下兼容问题)
2012/05/29 PHP
php/JS实现的生成随机密码(验证码)功能示例
2019/06/06 PHP
JQuery自定义事件的应用 JQuery最佳实践
2010/08/01 Javascript
把字符串按照特定的字母顺序进行排序的js代码
2014/01/28 Javascript
AngularJS语法详解(续)
2015/01/23 Javascript
javascript实现简单的ajax封装示例
2016/12/28 Javascript
Vue自定义指令拖拽功能示例
2017/02/17 Javascript
JavaScript定义函数的三种实现方法
2017/09/23 Javascript
结合mint-ui移动端下拉加载实践方法总结
2017/11/08 Javascript
通过jQuery学习js类型判断的技巧
2019/05/27 jQuery
在Vue项目中使用Typescript的实现
2019/12/19 Javascript
基于Vue CSR的微前端实现方案实践
2020/05/27 Javascript
解决基于 keep-alive 的后台多级路由缓存问题
2020/12/23 Javascript
[12:36]《DOTA2》国服注册与激活指南全攻略
2013/04/28 DOTA
[01:11:37]完美世界DOTA2联赛PWL S2 SZ vs FTD.C 第一场 11.19
2020/11/19 DOTA
python连接MySQL、MongoDB、Redis、memcache等数据库的方法
2013/11/15 Python
python查找第k小元素代码分享
2013/12/18 Python
Python素数检测的方法
2015/05/11 Python
python中import reload __import__的区别详解
2017/10/16 Python
在python中使用正则表达式查找可嵌套字符串组
2017/10/24 Python
Python 中的range(),以及列表切片方法
2018/07/02 Python
详解pandas的外部数据导入与常用方法
2019/05/01 Python
如何通过50行Python代码获取公众号全部文章
2019/07/12 Python
django+tornado实现实时查看远程日志的方法
2019/08/12 Python
python3实现的zip格式压缩文件夹操作示例
2019/08/17 Python
FFrpc python客户端lib使用解析
2019/08/24 Python
Python 如何定义匿名或内联函数
2020/08/01 Python
html5构建触屏网站之touch事件介绍
2013/01/07 HTML / CSS
NFL墨西哥官方商店:Tienda NFL
2017/11/28 全球购物
前台接待的工作职责
2013/11/21 职场文书
2014年十一国庆向国旗敬礼寄语
2014/04/11 职场文书
四风问题自我剖析材料
2014/10/07 职场文书
申报材料格式
2014/12/30 职场文书
给老婆的检讨书
2015/01/27 职场文书
大学运动会加油稿
2015/07/22 职场文书
孕妇病假条怎么写
2015/08/17 职场文书