浅谈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 相关文章推荐
JavaScript 高级篇之函数 (四)
Apr 07 Javascript
IE8提示Invalid procedure call or argument 异常的解决方法
Sep 30 Javascript
jquery获取div宽度的实现思路与代码
Jan 13 Javascript
javascript实现tabs选项卡切换效果(扩展版)
Mar 19 Javascript
jQuery 处理页面的事件详解
Jan 20 Javascript
JavaScript基本的输出和嵌入式写法教程
Oct 20 Javascript
原生JS简单实现ajax的方法示例
Nov 29 Javascript
微信小程序实现tab和swiper切换结合效果
Jul 17 Javascript
Vue.js实现备忘录功能
Jun 26 Javascript
JavaScript find()方法及返回数据实例
Apr 30 Javascript
解决Vue watch里调用方法的坑
Nov 07 Javascript
Vue 数据绑定的原理分析
Nov 16 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
Terran兵种介绍
2020/03/14 星际争霸
PHP基于phpqrcode生成带LOGO图像的二维码实例
2015/07/10 PHP
简单谈谈PHP面向对象之标识对象
2017/06/27 PHP
PHP生成(支持多模板)二维码海报代码
2018/04/30 PHP
php文件包含的几种方式总结
2019/09/19 PHP
跨浏览器的事件对象介绍
2012/06/27 Javascript
jQuery动态添加删除select项(实现代码)
2013/09/03 Javascript
用jquery中插件dialog实现弹框效果实例代码
2013/11/15 Javascript
利用JS判断用户是否上网(连接网络)
2013/12/23 Javascript
纯javascript模仿微信打飞机小游戏
2015/08/20 Javascript
详解js中class的多种函数封装方法
2016/01/03 Javascript
Node.js检测端口(port)是否被占用的简单示例
2016/09/29 Javascript
vue loadmore 组件滑动加载更多源码解析
2017/07/19 Javascript
详解用Node.js实现Restful风格webservice
2017/09/29 Javascript
vue组件实现进度条效果
2018/06/06 Javascript
vue项目动态设置页面title及是否缓存页面的问题
2018/11/08 Javascript
jquery ui 实现 tab标签功能示例【测试可用】
2019/07/25 jQuery
JS实现可用滑块滑动的缓动图代码
2019/09/01 Javascript
微信小程序点击列表跳转到对应详情页过程解析
2019/09/26 Javascript
基于aotu.js实现微信自动添加通讯录中的联系人功能
2020/05/28 Javascript
浅析vue中的nextTick
2020/12/28 Vue.js
Python基于Tkinter实现的记事本实例
2015/06/17 Python
Python基于pycrypto实现的AES加密和解密算法示例
2018/04/10 Python
Pandas中把dataframe转成array的方法
2018/04/13 Python
3分钟学会一个Python小技巧
2018/11/23 Python
python实现flappy bird小游戏
2018/12/24 Python
python如何调用php文件中的函数详解
2020/12/29 Python
Python字符串对齐、删除字符串不需要的内容以及格式化打印字符
2021/01/23 Python
微信端html5页面调用分享接口示例
2018/03/14 HTML / CSS
美国运动鞋类和服装零售连锁店:Shoe Palace
2019/08/13 全球购物
商务考察邀请函范文
2014/01/21 职场文书
广播体操口号
2014/06/18 职场文书
IT工程师岗位职责
2014/07/04 职场文书
党校党性分析材料
2014/12/19 职场文书
go语言中切片与内存复制 memcpy 的实现操作
2021/04/27 Golang
springboot临时文件存储目录配置方式
2021/07/01 Java/Android