浅谈Vue-cli 命令行工具分析


Posted in Javascript onNovember 22, 2017

Vue.js 提供一个官方命令行工具,可用于快速搭建大型单页应用。vue-webpack-boilerplate,官方定义为:

full-featured Webpack setup with hot-reload, lint-on-save, unit testing & css extraction.

目录结构:

├── README.md
├── build
│  ├── build.js
│  ├── utils.js
│  ├── vue-loader.conf.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

config 环境配置

config 配置文件用来配置 devServer 的相关设定,通过配置 NODE_ENV 来确定使用何种模式(开发、生产、测试或其他)

config
|- index.js #配置文件
|- dev.env.js #开发模式
|- prod.env.js #生产模式

index.js

'use strict'
const path = require('path');

module.exports = {
 dev: {

  // 路径
  assetsSubDirectory: 'static', // path:用来存放打包后文件的输出目录
  assetsPublicPath: '/', // publicPath:指定资源文件引用的目录
  proxyTable: {}, // 代理示例: proxy: [{context: ["/auth", "/api"],target: "http://localhost:3000",}]

  // 开发服务器变量设置
  host: 'localhost',
  port: 8080,
  autoOpenBrowser: true, // 自动打开浏览器devServer.open
  errorOverlay: true, // 浏览器错误提示 devServer.overlay
  notifyOnErrors: true, // 配合 friendly-errors-webpack-plugin
  poll: true, // 使用文件系统(file system)获取文件改动的通知devServer.watchOptions

  // source map
  cssSourceMap: false, // develop 下不生成 sourceMap
  devtool: 'eval-source-map' // 增强调试 可能的推荐值:eval, eval-source-map(推荐), cheap-eval-source-map, cheap-module-eval-source-map 详细:https://doc.webpack-china.org/configuration/devtool
 },
 build: {
  // index模板文件
  index: path.resolve(__dirname, '../dist/index.html'),

  // 路径
  assetsRoot: path.resolve(__dirname, '../dist'),
  assetsSubDirectory: 'static',
  assetsPublicPath: '/',

  // bundleAnalyzerReport
  bundleAnalyzerReport: process.env.npm_config_report,

  // Gzip
  productionGzip: false, // 默认 false
  productionGzipExtensions: ['js', 'css'],

  // source map
  productionSourceMap: true, // production 下是生成 sourceMap
  devtool: '#source-map' // devtool: 'source-map' ?
 }
}

dev.env.js

'use strict'
const merge = require('webpack-merge');
const prodEnv = require('./prod.env');

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"'
});
prod.env.js
'use strict'
module.exports = {
  NODE_ENV: '"production"'
};

build Webpack配置

build
|- utils.js #代码段
|- webpack.base.conf.js #基础配置文件
|- webpack.dev.conf.js #开发模式配置文件
|- webpack.prod.conf.js #生产模式配置文件
|- build.js #编译入口

实用代码段 utils.js

const config = require('../config')
const path = require('path')

exports.assetsPath = function (_path) {
  const assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory // 'static'
    : config.dev.assetsSubDirectory
  return path.posix.join(assetsSubDirectory, _path) // posix方法修正路径
}

exports.cssLoaders = function (options) { // 示例: ({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
 options = options || {};

 // cssLoader
 const cssLoader = {
  loader: 'css-loader',
  options: { sourceMap: options.sourceMap }
 }
 // postcssLoader
 var postcssLoader = {
  loader: 'postcss-loader',
  options: { sourceMap: options.sourceMap }
 }

 // 生成 loader
 function generateLoaders (loader, loaderOptions) {
  const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] // 设置默认loader
  if (loader) {
   loaders.push({
    loader: loader + '-loader',
    options: Object.assign({}, loaderOptions, { // 生成 options 对象
     sourceMap: options.sourceMap
    })
   })
  }

  // 生产模式中提取css
  if (options.extract) { // 如果 options 中的 extract 为 true 配合生产模式
   return ExtractTextPlugin.extract({
    use: loaders,
    fallback: 'vue-style-loader' // 默认使用 vue-style-loader
   })
  } else {
   return ['vue-style-loader'].concat(loaders)
  }
 }

 return { // 返回各种 loaders 对象
  css: generateLoaders(),
  postcss: generateLoaders(),
  less: generateLoaders('less'), 
  // 示例:[
  // { loader: 'css-loader', options: { sourceMap: true/false } },
  // { loader: 'postcss-loader', options: { sourceMap: true/false } },
  // { loader: 'less-loader', options: { sourceMap: true/false } },
  // ]
  sass: generateLoaders('sass', { indentedSyntax: true }),
  scss: generateLoaders('sass'),
  stylus: generateLoaders('stylus'),
  styl: generateLoaders('stylus')
 }
}

exports.styleLoaders = function (options) {
 const output = [];
 const loaders = exports.cssLoaders(options);
 for (const extension in loaders) {
  const loader = loaders[extension]
  output.push({
    test: new RegExp('\\.' + extension + '$'),
   use: loader
  })
  // 示例:
  // {
  //  test: new RegExp(\\.less$),
  //  use: {
  //   loader: 'less-loader', options: { sourceMap: true/false }
  //  }
  // }
 }
 return output
}

exports.createNotifierCallback = function () { // 配合 friendly-errors-webpack-plugin
 // 基本用法:notifier.notify('message');
 const notifier = require('node-notifier'); // 发送跨平台通知系统

 return (severity, errors) => {
  // 当前设定是只有出现 error 错误时触发 notifier 发送通知
  if (severity !== 'error') { return } // 严重程度可以是 'error' 或 'warning'
  const error = errors[0]

  const filename = error.file && error.file.split('!').pop();
  notifier.notify({
   title: pkg.name,
   message: severity + ': ' + error.name,
   subtitle: filename || ''
   // icon: path.join(__dirname, 'logo.png') // 通知图标
  })
 }
}

基础配置文件 webpack.base.conf.js

基础的 webpack 配置文件主要根据模式定义了入口出口,以及处理 vue, babel 等的各种模块,是最为基础的部分。其他模式的配置文件以此为基础通过 webpack-merge 合并。

'use strict'
const path = require('path');
const utils = require('./utils');
const config = require('../config');

function resolve(dir) {
 return path.join(__dirname, '..', dir);
}

module.exports = {
 context: path.resolve(__dirname, '../'), // 基础目录
 entry: {
  app: './src/main.js'
 },
 output: {
  path: config.build.assetsRoot, // 默认'../dist'
  filename: '[name].js',
  publicPath: process.env.NODE_ENV === 'production'
  ? config.build.assetsPublicPath // 生产模式publicpath
  : config.dev.assetsPublicPath // 开发模式publicpath
 },
 resolve: { // 解析确定的拓展名,方便模块导入
  extensions: ['.js', '.vue', '.json'], 
  alias: {  // 创建别名
   'vue$': 'vue/dist/vue.esm.js', 
   '@': resolve('src') // 如 '@/components/HelloWorld'
  }
 },
 module: {
  rules: [{
    test: /\.vue$/, // vue 要在babel之前
    loader: 'vue-loader',
    options: vueLoaderConfig //可选项: vue-loader 选项配置
   },{
    test: /\.js$/, // babel
    loader: 'babel-loader',
    include: [resolve('src')]
   },{ // url-loader 文件大小低于指定的限制时,可返回 DataURL,即base64
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, // url-loader 图片
    loader: 'url-loader',
    options: { // 兼容性问题需要将query换成options
     limit: 10000, // 默认无限制
     name: utils.assetsPath('img/[name].[hash:7].[ext]') // hash:7 代表 7 位数的 hash
    }
   },{
    test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, // url-loader 音视频
    loader: 'url-loader',
    options: {
     limit: 10000,
     name: utils.assetsPath('media/[name].[hash:7].[ext]')
    }
   },{
    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, // url-loader 字体
    loader: 'url-loader',
    options: {
     limit: 10000,
     name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
    }
   }
  ]
 },
 node: { // 是否 polyfill 或 mock
  setImmediate: false,
  dgram: 'empty',
  fs: 'empty',
  net: 'empty',
  tls: 'empty',
  child_process: 'empty'
 }
}

开发模式配置文件 webpack.dev.conf.js

开发模式的配置文件主要引用了 config 对于 devServer 的设定,对 css 文件的处理,使用 DefinePlugin 判断是否生产环境,以及其他一些插件。

'use strict'
const webpack = require('webpack');
const config = require('../config');
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const portfinder = require('portfinder'); // 自动检索下一个可用端口
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); // 友好提示错误信息

const devWebpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
    // 自动生成了 css, postcss, less 等规则,与自己一个个手写一样,默认包括了 css 和 postcss 规则
  },

  devtool: config.dev.devtool,// 添加元信息(meta info)增强调试

  // devServer 在 /config/index.js 处修改
  devServer: {
    clientLogLevel: 'warning', // console 控制台显示的消息,可能的值有 none, error, warning 或者 info
    historyApiFallback: true, // History API 当遇到 404 响应时会被替代为 index.html
    hot: true, // 模块热替换
    compress: true, // gzip
    host: process.env.HOST || config.dev.host, // process.env 优先
    port: process.env.PORT || config.dev.port, // process.env 优先
    open: config.dev.autoOpenBrowser, // 是否自动打开浏览器
    overlay: config.dev.errorOverlay ? { // warning 和 error 都要显示
      warnings: true,
      errors: true,
    } : false,
    publicPath: config.dev.assetsPublicPath, // 配置publicPath
    proxy: config.dev.proxyTable, // 代理
    quiet: true, // 控制台是否禁止打印警告和错误 若使用 FriendlyErrorsPlugin 此处为 true
    watchOptions: {
      poll: config.dev.poll, // 文件系统检测改动
    }
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': require('../config/dev.env') // 判断生产环境或开发环境
    }),
    new webpack.HotModuleReplacementPlugin(), // 热加载
    new webpack.NamedModulesPlugin(), // 热加载时直接返回更新的文件名,而不是id
    new webpack.NoEmitOnErrorsPlugin(), // 跳过编译时出错的代码并记录下来,主要作用是使编译后运行时的包不出错
    new HtmlWebpackPlugin({ // 该插件可自动生成一个 html5 文件或使用模板文件将编译好的代码注入进去
      filename: 'index.html',
      template: 'index.html',
      inject: true // 可能的选项有 true, 'head', 'body', false
    }),
  ]
})

module.exports = new Promise((resolve, reject) => {
 portfinder.basePort = process.env.PORT || config.dev.port; // 获取当前设定的端口
 portfinder.getPort((err, port) => {
  if (err) { reject(err) } else {
   process.env.PORT = port; // process 公布端口
   devWebpackConfig.devServer.port = port; // 设置 devServer 端口

   devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ // 错误提示插件
    compilationSuccessInfo: {
     messages: [`Your application is running here: http://${config.dev.host}:${port}`],
    },
    onErrors: config.dev.notifyOnErrors ? utils.createNotifierCallback() : undefined
   }))

   resolve(devWebpackConfig);
  }
 })
})

生产模式配置文件 webpack.prod.conf.js

'use strict'
const path = require('path');
const utils = require('./utils');
const webpack = require('webpack');
const config = require('../config');
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin');

const env = process.env.NODE_ENV === 'production'
 ? require('../config/prod.env')
 : require('../config/dev.env')

const webpackConfig = merge(baseWebpackConfig, {
 module: {
  rules: utils.styleLoaders({
   sourceMap: config.build.productionSourceMap, // production 下生成 sourceMap
   extract: true, // util 中 styleLoaders 方法内的 generateLoaders 函数
   usePostCSS: true
  })
 },
 devtool: config.build.productionSourceMap ? config.build.devtool : false,
 output: {
  path: config.build.assetsRoot,
  filename: utils.assetsPath('js/[name].[chunkhash].js'),
  chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
 },
 plugins: [
  new webpack.DefinePlugin({ 'process.env': env }),
  new webpack.optimize.UglifyJsPlugin({ // js 代码压缩还可配置 include, cache 等,也可用 babel-minify
   compress: { warnings: false },
   sourceMap: config.build.productionSourceMap,
   parallel: true // 充分利用多核cpu
  }),
  // 提取 js 文件中的 css
  new ExtractTextPlugin({
   filename: utils.assetsPath('css/[name].[contenthash].css'),
   allChunks: false,
  }),
  // 压缩提取出的css
  new OptimizeCSSPlugin({
   cssProcessorOptions: config.build.productionSourceMap
   ? { safe: true, map: { inline: false } }
   : { safe: true }
  }),
  // 生成 html
  new HtmlWebpackPlugin({
   filename: process.env.NODE_ENV === 'production'
    ? config.build.index
    : 'index.html',
   template: 'index.html',
   inject: true,
   minify: {
    removeComments: true,
    collapseWhitespace: true,
    removeAttributeQuotes: true
   },
   chunksSortMode: 'dependency' // 按 dependency 的顺序引入
  }),
  new webpack.HashedModuleIdsPlugin(), // 根据模块的相对路径生成一个四位数的 hash 作为模块 id
  new webpack.optimize.ModuleConcatenationPlugin(), // 预编译所有模块到一个闭包中
  // 拆分公共模块
  new webpack.optimize.CommonsChunkPlugin({
   name: 'vendor',
   minChunks: function (module) {
    return (
     module.resource &&
     /\.js$/.test(module.resource) &&
     module.resource.indexOf(
      path.join(__dirname, '../node_modules')
     ) === 0
    )
   }
  }),
  new webpack.optimize.CommonsChunkPlugin({
   name: 'manifest',
   minChunks: Infinity
  }),
  new webpack.optimize.CommonsChunkPlugin({
   name: 'app',
   async: 'vendor-async',
   children: true,
   minChunks: 3
  }),

  // 拷贝静态文档
  new CopyWebpackPlugin([{
    from: path.resolve(__dirname, '../static'),
    to: config.build.assetsSubDirectory,
    ignore: ['.*']
  }])]
})

if (config.build.productionGzip) { // gzip 压缩
 const 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, // 10kb 以上大小的文件才压缩
   minRatio: 0.8 // 最小比例达到 .8 时才压缩
  })
 )
}

if (config.build.bundleAnalyzerReport) { // 可视化分析包的尺寸
 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}

module.exports = webpackConfig;

build.js 编译入口

'use strict'

process.env.NODE_ENV = 'production'; // 设置当前环境为生产环境
const ora = require('ora'); //loading...进度条
const rm = require('rimraf'); //删除文件 'rm -rf'
const chalk = require('chalk'); //stdout颜色设置
const webpack = require('webpack');
const path = require('path');
const config = require('../config');
const webpackConfig = require('./webpack.prod.conf');

const spinner = ora('正在编译...');
spinner.start();

// 清空文件夹
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');

  //error
  if (stats.hasErrors()) {
    console.log(chalk.red(' 编译失败出现错误.\n'));
    process.exit(1);
  }

  //完成
  console.log(chalk.cyan(' 编译成功.\n'))
  console.log(chalk.yellow(
   ' file:// 无用,需http(s)://.\n'
  ))
 })

})

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
基于mouseout和mouseover等类似事件的冒泡问题解决方法
Nov 18 Javascript
基于JavaScript实现动态添加删除表格的行
Feb 01 Javascript
JavaScript中闭包之浅析解读(必看篇)
Aug 25 Javascript
基于jquery trigger函数无法触发a标签的两种解决方法
Jan 06 jQuery
Vue触发式全局组件构建的方法
Nov 28 Javascript
Node.js 进程平滑离场剖析小结
Jan 24 Javascript
Vue 解决路由过渡动画抖动问题(实例详解)
Jan 05 Javascript
JS数组方法shift()、unshift()用法实例分析
Jan 18 Javascript
js根据后缀判断文件文件类型的代码
May 09 Javascript
vue 里面的 $forceUpdate() 强制实例重新渲染操作
Sep 21 Javascript
vue使用exif获取图片经纬度的示例代码
Dec 11 Vue.js
用JS写一个发布订阅模式
Nov 07 Javascript
JavaScript数据结构与算法之队列原理与用法实例详解
Nov 22 #Javascript
深入理解Vue官方文档梳理之全局API
Nov 22 #Javascript
初识 Vue.js 中的 *.Vue文件
Nov 22 #Javascript
chorme 浏览器记住密码后input黄色背景处理方法(两种)
Nov 22 #Javascript
基于JavaScript实现表格滚动分页
Nov 22 #Javascript
Vue.js表单标签中的单选按钮、复选按钮和下拉列表的取值问题
Nov 22 #Javascript
Vue官方文档梳理之全局配置
Nov 22 #Javascript
You might like
php 抽象类的简单应用
2011/09/06 PHP
浅析PHP中的字符串编码转换(自动识别原编码)
2013/07/02 PHP
PHP生成不重复标识符的方法
2014/11/21 PHP
php简单实现短网址(短链)还原的方法(测试可用)
2016/05/09 PHP
PHP利用pdo_odbc实现连接数据库示例【基于ThinkPHP5.1搭建的项目】
2019/05/13 PHP
php使用json-schema模块实现json校验示例
2019/09/28 PHP
jQuery get和post 方法传值注意事项
2009/11/03 Javascript
jQuery页面图片伴随滚动条逐渐显示的小例子
2013/03/21 Javascript
js replace 与replaceall实例用法详解
2013/08/03 Javascript
vue-cli中的webpack配置详解
2017/09/25 Javascript
JavaScript创建对象的常用方式总结
2018/08/10 Javascript
vue操作下拉选择器获取选择的数据的id方法
2018/08/24 Javascript
微信小程序实现发送验证码按钮效果
2018/12/20 Javascript
抖音上用记事本编写爱心小程序教程
2019/04/17 Javascript
js实现简单扫雷
2020/11/27 Javascript
JavaScript中条件语句的优化技巧总结
2020/12/04 Javascript
js 执行上下文和作用域的相关总结
2021/02/08 Javascript
[43:57]Liquid vs Mineski 2019国际邀请赛小组赛 BO2 第二场 8.16
2019/08/19 DOTA
Python实现对excel文件列表值进行统计的方法
2015/07/25 Python
Python获取系统所有进程PID及进程名称的方法示例
2018/05/24 Python
python学生管理系统学习笔记
2019/03/19 Python
python实现飞船游戏的纵向移动
2020/04/24 Python
Python实现钉钉/企业微信自动打卡的示例代码
2021/02/02 Python
Django项目在pycharm新建的步骤方法
2021/03/02 Python
美国一家主营日韩美妆护肤品的在线商店:iMomoko
2016/09/11 全球购物
幼师专业求职推荐信
2013/11/08 职场文书
物流管理专业应届生求职信
2013/11/21 职场文书
职业生涯规划怎么写
2013/12/29 职场文书
学习十八大报告感言
2014/02/28 职场文书
外联部演讲稿
2014/05/24 职场文书
甲乙双方合作协议书
2014/10/13 职场文书
销售督导岗位职责
2015/04/10 职场文书
工作迟到检讨书范文
2015/05/06 职场文书
刑事法律意见书
2015/06/04 职场文书
入党自传范文2015
2015/06/26 职场文书
python xlwt模块的使用解析
2021/04/13 Python