内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

详解vue-cli2.0配置文件

2017-10-12 17:02 出处:清屏网 人气: 评论(0

大家拿到一个项目,要快速上手,正确的思路是这样的:

首先,如果在项目有readme.md的情况下,大家要先读readme,项目的一些基本介绍,包括项目信息、运行的脚本、采用何种框架,以及项目维护者等信息通常都会有。一般在git上维护的项目都会有readme.md,不熟悉markdown语法的同学可以先了解下 markdown入门

第二步,要看package.json。现代的前端项目中通常都会有package.json文件。在package.json里,会介绍项目名称、版本、描述、作者、脚本、依赖包,对环境的要求,以及对浏览器要求。

 1 {
 2   "name": "uccn",
 3   "version": "1.0.0",
 4   "description": "uccn3.0",
 5   "author": "v_yangtianjiao <v_yangtianjiao@baidu.com>",
 6   "private": true,
   // 这里的脚本是分析项目的主要入口
 7   "scripts": {
 8     "dev": "node build/dev-server.js",
 9     "start": "node build/dev-server.js",
10     "build": "node build/build.js",
11     "jsonp": "node build/jsonp-server.js"
12   },
   // 项目依赖
13   "dependencies": {
14     "fetch-jsonp": "^1.1.3",
15     "less": "^2.7.2",
16     "less-loader": "^4.0.4",
17     "stylus": "^0.54.5",
18     "stylus-loader": "^3.0.1",
19     "vue": "^2.4.2"
20   },
21   "devDependencies": {
22     "autoprefixer": "^7.1.2",
23     "babel-core": "^6.22.1",
24     "babel-loader": "^7.1.1",
25     "babel-plugin-component": "^0.10.1",
26     "babel-plugin-transform-runtime": "^6.22.0",
27     "babel-preset-env": "^1.3.2",
28     "babel-preset-es2015": "^6.24.1",
29     "babel-preset-stage-2": "^6.22.0",
30     "babel-register": "^6.22.0",
31     "chalk": "^2.0.1",
32     "connect-history-api-fallback": "^1.3.0",
33     "copy-webpack-plugin": "^4.0.1",
34     "css-loader": "^0.28.0",
35     "cssnano": "^3.10.0",
36     "eventsource-polyfill": "^0.9.6",
37     "express": "^4.14.1",
38     "extract-text-webpack-plugin": "^2.0.0",
39     "file-loader": "^0.11.1",
40     "friendly-errors-webpack-plugin": "^1.1.3",
41     "html-webpack-plugin": "^2.28.0",
42     "http-proxy-middleware": "^0.17.3",
43     "opn": "^5.1.0",
44     "optimize-css-assets-webpack-plugin": "^2.0.0",
45     "ora": "^1.2.0",
46     "rimraf": "^2.6.0",
47     "semver": "^5.3.0",
48     "shelljs": "^0.7.6",
49     "url-loader": "^0.5.8",
50     "vue-loader": "^13.0.4",
51     "vue-style-loader": "^3.0.1",
52     "vue-template-compiler": "^2.4.2",
53     "webpack": "^2.6.1",
54     "webpack-bundle-analyzer": "^2.2.1",
55     "webpack-dev-middleware": "^1.10.0",
56     "webpack-hot-middleware": "^2.18.0",
57     "webpack-merge": "^4.1.0"
58   },
     // 对node版本的以及npm版本的要求
59   "engines": {
60     "node": ">= 4.0.0",
61     "npm": ">= 3.0.0"
62   },
   // 浏览器要求,vue项目不支持ie8,因为ie8是es3,尚没有Object.defineProperty属性
63   "browserslist": [
64     "> 1%",
65     "last 2 versions",
66     "not ie <= 8"
67   ]
68 }

上面的package.json是从实际vue项目中摘出来的,大家从package.json中就会对项目有一个大概的了解,最主要的是脚本部分。通过npm的自动化任务,可以很方便的执行配置文件中的脚本。通过配置  "jsonp": "node build/jsonp-server.js",可以方便的使用npm run jsonp命令,代替node build/jsonp-server.js或者更复杂的一系列命令。详细的npm自动化命令可以移步 npm 自动化

现在的项目目录结构如上,我们从刚才的脚本入手。首先是启服务的脚本npm run dev,实际上是执行node build/dev-server.js,我们在build文件夹中找到dev-server.js,一步步分析。

/* eslint-disable */

// 首先检查node和npm的版本
require('./check-versions')()

// 获取配置文件中默认的配置
var config = require('../config')
// 如果node无法判断当前是开发环境还是生产环境,则使用config.dev.env.NODE_ENV作为当前的环境
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}

var opn = require('opn')// 用来在起来服务之后,打开浏览器并跳转指定URL
var path = require('path')// node自带文件路径工具
var express = require('express')// node框架express(本地开发的核心,起服务)
var webpack = require('webpack')// webpack,压缩打包
var proxyMiddleware = require('http-proxy-middleware')// 中间件
var webpackConfig = require('./webpack.dev.conf')// 开发环境的webpack配置
var mockMiddleware = require('../config/dev.mock')// 开发环境本地mock数据中间件

var port = process.env.PORT || config.dev.port
var autoOpenBrowser = !!config.dev.autoOpenBrowser
var proxyTable = config.dev.proxyTable

var app = express()// 起服务
var compiler = webpack(webpackConfig)// webpack进行编译

// webpack-dev-middleware将编译的文件放在内存中,后续注入
var devMiddleware = require('webpack-dev-middleware')(compiler, {
  publicPath: webpackConfig.output.publicPath,
  quiet: true
})
// 热加载
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
  log: false,
  heartbeat: 2000
})
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(options.filter || context, options))
})

// 处理后退的时候匹配资源
app.use(require('connect-history-api-fallback')())

// 暂存在内存的webpack编译后的文件挂载到express上
app.use(devMiddleware)

// 将本地mock中间件挂载到express上
app.use(mockMiddleware);

// 热加载挂载到express上
app.use(hotMiddleware)

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

var uri = 'http://localhost:' + port

var _resolve
var readyPromise = new Promise(resolve => {
  _resolve = resolve
})

console.log('> Starting dev server...')
devMiddleware.waitUntilValid(() => {
  console.log('> Listening at ' + uri + '\n')
  if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
    opn(uri)
  }
  _resolve()
})
// 通过配置的端口,自动打开浏览器,并跳转拼好的URL,至此,发开环境已经跑起来了
var server = app.listen(port)

module.exports = {
  ready: readyPromise,
  close: () => {
    server.close()
  }
}

在上面的dev-server中,有很多变量来自于./config/index.js和webpack.dev.conf.js,我们一个个看上述配置文件。

首先看./config/index.js,这里是整个项目主要的配置入口,我们在代码中一步步分析:

// node自带路径工具.
var path = require('path')
// 分为两种环境,dev和production
module.exports = {
  build: {
    env: require('./prod.env'),// 使用config/prod.env.js中定义的编译环境
    index: path.resolve(__dirname, '../dist/index.html'),// 编译输入的index.html文件。node.js中,在任何模块文件内部,可以使用__filename变量获取当前模块文件的带有完整绝对路径的文件名,
    assetsRoot: path.resolve(__dirname, '../dist'),// 编译输出的静态资源路径
    assetsSubDirectory: 'static',// 编译输出的二级目录
    assetsPublicPath: './', // 编译发布的根目录,可配置为资源服务器或者cdn域名
    productionSourceMap: false,//是否开启cssSourceMap
    productionGzip: false,// 是否开启gzip
    productionGzipExtensions: ['js', 'css'],// 需要用gzip压缩的文件扩展名
    bundleAnalyzerReport: process.env.npm_config_report
  },
  dev: {
    env: require('./dev.env'),
    port: 8989,// 起服务的端口
    autoOpenBrowser: true,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},// 需要代理的接口,可以跨域
    cssSourceMap: false
  }
}

接着我们分析webpack.dev.conf.js:

var utils = require('./utils')// 工具类
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')// 使用webpack配置合并插件
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')// 这个插件自动生成HTML,并注入到.html文件中
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')

// 将hot-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: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
  },
  // webpack-devtool有7种模式,cheap-module-eval-source-map模式是比较快的开发模式
 
  devtool: '#cheap-module-eval-source-map',
  plugins: [
  // 你可以理解为,通过配置了DefinePlugin,那么这里面的标识就相当于全局变量,你的业务代码可以直接使用配置的标识。
    new webpack.DefinePlugin({
      'process.env': config.dev.env
    }),
    // hotModule插件让页面变动时,只重绘对应的模块,不会重绘整个HTML文件
    new webpack.HotModuleReplacementPlugin(),
  // 在编译出现错误时,使用 NoEmitOnErrorsPlugin 来跳过输出阶段。这样可以确保输出资源不会包含错误
    new webpack.NoEmitOnErrorsPlugin(),
    // 将生成的HTML代码注入index.html文件
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
  // friendly-errors-webpack-plugin用于更友好地输出webpack的警告、错误等信息
    new FriendlyErrorsPlugin()
  ]
})

刚才的webpack.dev.conf.js中有引到webpack.base.conf.js,我们就把他们一网打尽,继续看webpack.base.conf.js!

/* eslint-disable */
var path = require('path')// node自带的文件路径插件
var utils = require('./utils')// 工具类
var config = require('../config')// 上面说过的config/index
var vueLoaderConfig = require('./vue-loader.conf')// vue-loader.conf配置文件是用来解决各种css文件的,定义了诸如css,less,sass之类的和样式有关的loader
// 此函数是用来返回当前目录的平行目录的路径,
function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

module.exports = {
  entry: {
    uccn: './src/main.js'// 入口
  },
  output: {
  // 路径是config目录下的index.js中的build配置中的assetsRoot,也就是dist目录
    path: config.build.assetsRoot,
    filename: '[name].js',
  // 上线地址,也就是真正的文件引用路径,如果是production生产环境,其实这里都是 '/'
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
 // resolve是webpack的内置选项,顾名思义,决定要做的事情,也就是说当使用 import "jquery",该如何去执行这件事情,就是resolve配置项要做的,import jQuery from "./additional/dist/js/jquery" 这样会很麻烦,可以起个别名简化操作
  resolve: {
  // 省略扩展名,比方说import index form '../js/index', 会默认去找index文件,然后找index.js,.vue,.josn.
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
    // 使用上面的resolve函数,意思是用@代替src的绝对路径
      '@': resolve('src'),
    }
  },
 // 不同的模块使用不同的loader
  module: {
    rules: [
      {
     // 对vue文件,使用vue-loader解析
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
     // babel-loader把es6解析成es5
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test')]
      },
      {
     // url-loader将文件大小低于下面option中limit的图片,转化为一个64位的DataURL,这样会省去很多请求,大于limit的,按[name].[hash:7].[ext]的命名方式放到了static/img下面,方便做cache
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 20000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
     // 音频和视频文件处理,同上
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
     // 字体处理,同上 
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  }
}

至此, npm run dev 起本地开发环境相关的配置文件基本说完了,接着说一下上面都用到的util工具类:

var path = require('path')
var config = require('../config')
// extract-text-webpack-plugin该插件的主要是为了抽离css样式,防止将样式打包在js中引起页面样式加载错乱的现象
var ExtractTextPlugin = require('extract-text-webpack-plugin')

// 返回资源文件路径,path.posix以posix兼容的方式交互,是跨平台的,如果是path.win32的话,只能在win上
exports.assetsPath = function (_path) {
  var assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory
  return path.posix.join(assetsSubDirectory, _path)
}

// 通过判断是否是生产环境,配置不同的样式语言的loader配置
exports.cssLoaders = function (options) {
  options = options || {}

  var cssLoader = {
    loader: 'css-loader',
    options: {
      minimize: process.env.NODE_ENV === 'production',
      sourceMap: options.sourceMap
    }
  }

  // 生成各种loader配置,通过传入不同的loader和option,将不同样式文件语言的loader拼好,push到loader配置中。
  function generateLoaders (loader, loaderOptions) {
    var loaders = [cssLoader]
    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }

    // extract-text-webpack-plugin有三个参数,use指需要用什么loader去编译文件;fallback指编译后用什么loader去提取文件;还有一个publicfile用来覆盖项目路径
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }

  // 对不同的样式语言,返回相应的loader
  return {
    css: generateLoaders(),
    postcss: generateLoaders(),
    less: generateLoaders('less'),
    sass: generateLoaders('sass', { indentedSyntax: true }),
    scss: generateLoaders('sass'),
    stylus: generateLoaders('stylus'),
    styl: generateLoaders('stylus')
  }
}

// 生成处理不同的样式文件处理规则
exports.styleLoaders = function (options) {
  var output = []
  var loaders = exports.cssLoaders(options)
  for (var extension in loaders) {
    var loader = loaders[extension]
    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }
  return output
}

———————————————— 华丽的分隔符 —————————————————

下面我们继续说npm run build,打包编译的一系列操作~

从package.json 中可以看出,npm run build,其实是执行了 node build/build.js,我们在build文件夹中找到build.js,build主要的工作是: 检测node和npm版本,删除dist包,webpack构建打包,在终端输出构建信息并结束,如果报错,则输出报错信息。

require('./check-versions')()

process.env.NODE_ENV = 'production'

// 在终端显示的旋转器插件
var ora = require('ora')
// 用于删除文件夹
var rm = require('rimraf')
var path = require('path')
// 终端文字颜色插件
var chalk = require('chalk')
var webpack = require('webpack')
var config = require('../config')
var webpackConfig = require('./webpack.prod.conf')

var spinner = ora('building for production...')
spinner.start()

// 删除dist文件夹,之后webpack打包
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), 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('  Build failed with errors.\n'))
      process.exit(1)
    }

    console.log(chalk.cyan('  Build complete.\n'))
    console.log(chalk.yellow(
      '  Tip: built files are meant to be served over an HTTP server.\n' +
      '  Opening index.html over file:// won\'t work.\n'
    ))
  })
})

build.js用到了webpack.prod.conf.js,他与webpack.base.conf.js merge之后,作为webpack配置文件,我们再看看webpack.prod.conf.js,主要做的工作是:
1.提取webpack生成的bundle中的文本,到特定的文件,使得css,js文件与webpack输出的bundle分离。

2.合并基本的webpack配置

3.配置webpack的输出,包括输出路径,文件名格式。

4.配置webpack插件,包括丑化代码。

5.gzip下引入compression插件进行压缩。

/* eslint-disable */
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var CopyWebpackPlugin = require('copy-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
// 用于从webpack生成的bundle中提取文本到特定文件中的插件
// 可以抽取出css,js文件将其与webpack输出的bundle分离
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')

var env = config.build.env
// 合并基础的webpack配置
var webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true
    })
  },
 // 7中sourceMap上面有讲过
  devtool: config.build.productionSourceMap ? '#source-map' : false,
 // 配置webpack输出的目录,及文件命名规则
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].min.js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
 // webpack插件配置
  plugins: [
    // 同webpack.dev.conf.js
    new webpack.DefinePlugin({
      'process.env': env
    }),
  // 丑化代码
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      },
      sourceMap: true
    }),
    // 抽离css文件到单独的文件
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].min.css')
    }),
    new OptimizeCSSPlugin({
      cssProcessorOptions: {
        safe: true
      }
    }),
    // 生成并注入index.html
    new HtmlWebpackPlugin({
      filename: config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: false,
        removeAttributeQuotes: true
      },
      chunksSortMode: 'dependency'
    }),
    // keep module.id stable when vender modules does not change
    new webpack.HashedModuleIdsPlugin(),
    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']
    }),
    copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})
// gzip模式下需要引入compression插件进行压缩
if (config.build.productionGzip) {
  var CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

到此为止,vue官方脚手架工具vue-cli 2.0的所有配置文件都已介绍完毕,从头到尾再梳理一遍:

执行npm run dev或者npm run start,实际是在node环境执行build/dev-server.js, dev-server.js会去拿到config中的端口等配置,通过express起一个服务,通过插件自动打开浏览器,加载webpack编译后放在内存的bundle。

执行npm run build,实际上执行了build/build.js,通过webpack的一系列配置及插件,将文件打包合并丑化,并创建dist目录,放置编译打包后的文件,这将是未来用在生产环境的包。

写这篇文章我自身的收获也挺多,第一是对vue-cli整体的认知更加清晰条理,第二是对webpack的一些插件有了新的认识。以前对一些插件模棱两可,直接越过,这是不对的,要一步一个脚印儿,遇坑填坑,这样才会有收获。虽然过程可能是艰辛的,但收获将会是巨大的~

文章中不足之处希望大家多多指正!

参考文献:

extract-text-webpack-plugin 的使用及安装

vue-cli的webpack模板项目配置文件分析

webpack——devtool里的7种SourceMap模式

vue-cli#2.0 webpack 配置分析

__dirname与__filename

分享给小伙伴们:
本文标签: vue-clivue

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号