Vue 工程代码阅读小技巧

Vue 工程代码阅读小技巧

对于一个刚刚接触 vue 的 小白白,怎样才能高效的阅读别人的 vue 工程代码。当然,有的项目有工程介绍,这令当别论,你就老老实实地看文档吧,看完差不多也了解业务流程了,再看代码会更容易理解。

目录结构

首先,个人感觉必须了解 vue 工程的目录结构,就是你知道那个文件是干嘛的。幸好大多数 vue 项目的结构都差不多,命名方式也好阅读:

├── vue.config.js/ # webpack 配置文件;
├── config/ # 与项目构建相关的常用的配置选项;
│ ├── index.js # 主配置文件
│
├── src/
│ ├── main.js # webpack 的入口文件;
│ ├── App.vue # APP页面入口
│ ├── assets/ # 共用的代码以外的资源,如:图片、图标、视频 等;
│ ├── api/ # 网络模块,如:接口;
│ ├── router/ # 路由模块
│ ├── I18n/ # 国际化模块
│ ├── directive/ # 指令
│ ├── vuex/ # 组件共享状态
│ ├── libs/ # 工具
│ ├── components/ # 共用的组件;; 这里的存放的组件应该都是展示组件
│ │ ├── base/ # 基本组件,如:共用的弹窗组件,loading加载组件,提示组件。
│ ├── utils/ # 共用的资源,如:常用的图片、图标,共用的组件、模块、样式,常量文件等等;
│ │ ├── util/ # 自己封装的一些工具
│ │ └── ...
│ ├── layout # 公共局页面;
│ └── view/ # 存放项目业务代码;
│ ├── home # 首页;
│ ├── application-service # 应用服务
│ ├── image # 镜像
│ ├── team # 团队成员
│ ├── code-manage # 代码管理
├── App.vue # app 的根组件;
├── public/ # 纯静态资源,该目录下的文件不会被webpack处理,该目录会被拷贝到输出目录下;
├── .babelrc # babel 的配置文件
├── .editorconfig # 编辑器的配置文件;可配置如缩进、空格、制表类似的参数;
├── .eslintrc.js # eslint 的配置文件
├── .eslintignore # eslint 的忽略规则
├── .gitignore # git的忽略配置文件
├── jsconfig.json # 别名配置,用于ctrl+鼠标左建查看文件
├── package.json # npm包配置文件,里面定义了项目的npm脚本,依赖包等信息
└── README.md # 项目信息文档

1.1 项目目录介绍(VUE2)

目录/文件 说明 详解
build 项目构建(webpack)相关代码 详解
config 配置目录,包括端口号等 详解
node_modules npm 加载的项目依赖模块 详解
src 这里是我们要开发的目录,基本上要做的事情都在这个目录里。里面包含了几个目录及文件:  
♞ assets:放置一些图片,如 logo  
♞ components:放置一些组件  
♞ views:页面(视图)组件  
♞ router.js:路由脚本文件  
♞ App.vue:项目入口文件  
♞ main.js::项目的核心文件
详解
static 静态资源目录,如图片、字体等 详解
test 初始测试目录,可删除 详解
.xxxx 文件 这些是一些配置文件,包括语法配置,git 配置等
index.html 首页入口文件
package.json 项目配置文件 详解
README.md 项目的说明文档,markdown 格式
1.1.1 build 目录
目录/文件 说明
build.js 生产环境构建配置
check-versions.js 版本检查,npm 等
utils 构想相关工具
vue-loader.conf.js CSS 加载器配置
webpack.base.conf.js webpack 基础配置
webpack.dev.conf.js webpack 开发环境配置
webpack.prod.conf.js webpack 生产环境配置
1.1.1.1 build.js
// 此文件是 webpack 的基本项目配置文件
// "use strict" 指令在 JavaScript 1.8.5 (ECMAScript5) 中新增。它不是一条语句,但是是一个字面量表达式,在 JavaScript 旧版本中会被忽略。"use strict" 的目的是指定代码在严格条件下执行,严格模式下你不能使用未声明的变量。
'use strict'
// 版本检查 node 的版本号  版本有要求 "engines": {"node": ">= 4.0.0","npm": ">= 3.0.0"}
// 立即执行
require('./check-versions')()
// process 是 node 中的 global 全局对象的属性,process 是 node 中的全局变量,env 设置环境变量
process.env.NODE_ENV = 'production'
// ora 是一个命令行转圈圈动画插件,好看用的
const ora = require('ora')
// rimraf 插件是用来执行 UNIX 命令 rm 和 -rf 的用来删除文件夹和文件,清空旧的文件
const rm = require('rimraf')
// node.js 路径模块 连接路径
const path = require('path')
// chalk 插件,用来在命令行中显示彩色文字
const chalk = require('chalk')
// 引入 webpack 模块使用内置插件和 webpack 方法
const webpack = require('webpack')
// 引入 config 目录下的 index.js 配置文件
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')
// 开启转圈圈动画
const spinner = ora('building for production...')
spinner.start()
// 调用 rm 方法,第一个参数的结果就是 绝对/工程名/dist/static,表示删除这个路径下面的所有文件
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
  // 如果删除的过程中出现错误,就抛出这个错误,同时程序终止
  if (err) throw err
  // 没有错误,就执行 webpack 编译
  webpack(webpackConfig, (err, stats) => { 
    // 这个回调函数是 webpack 编译过程中执行
    // 停止转圈圈动画
    spinner.stop()
    // 如果有错误就抛出错误
    if (err) throw err
    // process.stdout 用来控制标准输出,stats对象中保存着编译过程中的各种消息
    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'
    ))
  })
})
1.1.1.2 check-versions.js
'use strict'
// chalk 插件,用来在命令行中显示彩色文字
const chalk = require('chalk')
// semver 插件,是用来判断特定的版本号
const semver = require('semver')
// 导入 package.json 文件,要使用里面的 engines
// 要注意 require 是直接可以导入 json 文件的,并且 requrie 返回的就是 json 对象
const packageConfig = require('../package.json')
// shelljs 插件,作用是用来执行 Unix 系统命令
const shell = require('shelljs')

function exec (cmd) {
  return require('child_process').execSync(cmd).toString().trim()
}

// 检查 node 版本信息
const versionRequirements = [
  {
    name: 'node',
    currentVersion: semver.clean(process.version),
    versionRequirement: packageConfig.engines.node
  }
]

// 检查 npm 版本信息
if (shell.which('npm')) {
  versionRequirements.push({
    name: 'npm',
    currentVersion: exec('npm --version'),
    versionRequirement: packageConfig.engines.npm
  })
}

// 输出提示
module.exports = function () {
  const warnings = []
  for (let i = 0; i < versionRequirements.length; i++) {
    const mod = versionRequirements[i]
    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
      warnings.push(mod.name + ': ' +
        chalk.red(mod.currentVersion) + ' should be ' +
        chalk.green(mod.versionRequirement)
      )
    }
  }

  if (warnings.length) {
    console.log('')
    console.log(chalk.yellow('To use this template, you must update following to modules:'))
    console.log()
    for (let i = 0; i < warnings.length; i++) {
      const warning = warnings[i]
      console.log('  ' + warning)
    }
    console.log()
    process.exit(1)
  }
}
1.1.1.3 utils.js
'use strict'
// 引入nodejs路径模块
const path = require('path')
// 引入 config 目录下的 index.js 配置文件
const config = require('../config')
// 引入 extract-text-webpack-plugin 插件,用来将 css 提取到单独的 css 文件中
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// 引入 package.json
const packageConfig = require('../package.json')
// exports 其实就是一个对象,用来导出方法的最终还是使用 module.exports,此处导出 assetsPath
exports.assetsPath = function (_path) {
  const assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory
  // 返回一个相对根路径
  return path.posix.join(assetsSubDirectory, _path)
}

// 导出 cssLoaders 的相关配置
exports.cssLoaders = function (options) {
  options = options || {}
    
  // cssLoader 的基本配置
  const cssLoader = {
    loader: 'css-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }
  
  // 为 css 自动生成兼容性前缀
  const postcssLoader = {
    loader: 'postcss-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }
  
  // generate loader string to be used with extract text plugin
  function generateLoaders (loader, loaderOptions) {
    // 将上面的基础 cssLoader 配置、兼容性前缀配置、自适应配置放在一个数组里面
    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]

    // 如果该函数传递了单独的 loader 就加到这个 loaders 数组里面,这个 loader 可能是 less,sass 之类的
    if (loader) {
      loaders.push({
        // 加载对应的 loader
        loader: loader + '-loader',
        // Object.assign 是 es6 的方法,主要用来合并对象的,浅拷贝
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }
    // Extract CSS when that option is specified
    // (which is the case during production build)
    // extract 是自定义的属性,可以定义在 options 里面
    // 主要作用就是当配置为 true 就把文件单独提取,false表示不单独提取,这个可以在使用的时候单独配置
    // 用来返回最终读取和导入 loader,来处理对应类型的文件
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }
  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
  return {
    // css 对应 vue-style-loader 和 css-loader
    css: generateLoaders(),
    // postcss 对应 vue-style-loader 和 css-loade
    postcss: generateLoaders(),
    // less 对应 vue-style-loader 和 less-loader
    less: generateLoaders('less'),
    // sass 对应 vue-style-loader 和 sass-loader
    sass: generateLoaders('sass', { indentedSyntax: true }),
    // scss 对应 vue-style-loader 和 sass-loader
    scss: generateLoaders('sass'),
    // stylus 对应 vue-style-loader 和 stylus-loader
    stylus: generateLoaders('stylus'),
    // styl 对应 vue-style-loader 和 styl-loader
    styl: generateLoaders('stylus')
  }
}

// Generate loaders for standalone style files (outside of .vue)
// 下面这个主要处理 import 这种方式导入的文件类型的打包,上面的 exports.cssLoaders 是为这一步服务的
exports.styleLoaders = function (options) {
  const output = []
  // 下面就是生成的各种 css 文件的 loader 对象
  const loaders = exports.cssLoaders(options)
  for (const extension in loaders) {
    // 把每一种文件的 laoder 都提取出来
    const loader = loaders[extension]
    output.push({
      // 把最终的结果都 push 到 output 数组中
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }
  return output
}
// node-notifier 是一个跨平台系统通知的页面,当遇到错误时,它能用系统原生的推送方式推送信息
exports.createNotifierCallback = () => {
  const notifier = require('node-notifier')
  return (severity, errors) => {
    if (severity !== 'error') return
    const error = errors[0]
    const filename = error.file && error.file.split('!').pop()
    notifier.notify({
      title: packageConfig.name,
      message: severity + ': ' + error.name,
      subtitle: filename || '',
      icon: path.join(__dirname, 'logo.png')
    })
  }
}
1.1.1.4 vue-loader.conf.js
'use strict'
// 引入配置文件
const utils = require('./utils')
const config = require('../config')
// 判断是不是生产环境
const isProduction = process.env.NODE_ENV === 'production'
// 根据环境来获取相应的 productionSourceMap 或者 cssSourceMap
const sourceMapEnabled = isProduction
  ? config.build.productionSourceMap
  : config.dev.cssSourceMap

module.exports = {
  // 配置在 .vue 文件中的 css 相关处理规则
  loaders: utils.cssLoaders({
    sourceMap: sourceMapEnabled,
    extract: isProduction
  }),

  // 记录压缩的代码,用来找到源码位置
  cssSourceMap: sourceMapEnabled,
  // 是否缓存破坏
  cacheBusting: config.dev.cacheBusting,
  // transformToRequire 的作用是在模块编译的过程中,编译器可以将某些属性,比如 src 转换为 require 调用
  transformToRequire: {
    video: ['src', 'poster'],
    source: 'src',
    img: 'src',
    image: 'xlink:href'
  }
}
1.1.1.5 webpack.base.conf.js
'use strict'
// 引入配置文件
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
 
// 封装函数返回 dir 的和 .. 和其绝对路径用 \ 拼接的路径
function resolve (dir) {
  return path.join(__dirname, '..', dir)
}
 
const createLintingRule = () => ({
  test: /\.(js|vue)$/,
  loader: 'eslint-loader',
  enforce: 'pre',
  include: [resolve('src'), resolve('test')],
  options: {
    formatter: require('eslint-friendly-formatter'),
    emitWarning: !config.dev.showEslintErrorsInOverlay
  }
})
 
module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {
    // 入口文件 多入口在这里添加
    app: './src/main.js'
  },
  // 输出
  output: {
     // 文件路径
    path: config.build.assetsRoot,
    // 文件名
    filename: '[name].js',
    // 静态资源路径 根据环境来改变
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  // 对模块进行解析
  resolve: {
    // 对模块的后缀进行解析 比如导入 index 没有写后缀名 那么会自动先匹配 .js > .vue > .json 后缀文件
    extensions: ['.js', '.vue', '.json'],
    // 别名,@ 代表 src 就是在这里配置的
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },
  // 解析不同的模块
  module: {
    rules: [
      ...(config.dev.useEslint ? [createLintingRule()] : []),
      {
        // 解析 .vue 后缀的文件
        test: /\.vue$/,
        // 用 vue-loader 去解析 .vue 后缀的文件
        loader: 'vue-loader',
        // 对解析文件的参数配置
        options: vueLoaderConfig
      },
      {
        // 解析后缀 .js 的文件
        test: /\.js$/,
        loader: 'babel-loader',
        // 解析的包含的文件路径
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          // 限制在 10000 以内
          limit: 10000,
          // 对输出的内容进行地址转换
          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]')
        }
      }
    ]
  },
  node: {
    // 阻止 webpack 注入无用的 setImmediate polyfill
    // prevent webpack from injecting useless setImmediate polyfill because Vue
    // source contains it (although only uses it if it's native).
    setImmediate: false,
    // prevent webpack from injecting mocks to Node native modules
    // that does not make sense for the client
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }
}
1.1.1.6 webpack.dev.conf.js
'use strict'
// 引入配置文件
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
// 拷贝资源的插件
const CopyWebpackPlugin = require('copy-webpack-plugin')
// 自动生成 html 的插件,能够把资源自动加载到 html 文件中
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 把 webpack 的错误和日志收集起来,更友好展示错误日志的插件
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// 自动打开可用端口的包
const portfinder = require('portfinder')
 
// 当前环境的 host
const HOST = process.env.HOST
// 当前环境的 port
const PORT = process.env.PORT && Number(process.env.PORT)
 
// 开发环境的配置
const devWebpackConfig = merge(baseWebpackConfig, {
  module: {
    // loader 的配置,具体内容可以参考 utils 文件
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
  },
  // cheap-module-eval-source-map is faster for development
  devtool: config.dev.devtool,
 
  // these devServer options should be customized in /config/index.js
  devServer: {
    // 重新加载 server 时,控制台对一些错误以 warning 的方式提示
    clientLogLevel: 'warning',
    // 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html
    historyApiFallback: {
      rewrites: [
        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
      ],
    },
    // 启用 webpack 的模块热替换特性
    hot: true,
    // 告诉服务器从哪里提供内容
    contentBase: false, // since we use CopyWebpackPlugin.
    // 是否压缩
    compress: true,
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    // 是否自动打开浏览器
    open: config.dev.autoOpenBrowser,
    // 编译出错时是否有提示
    overlay: config.dev.errorOverlay
      ? { warnings: false, errors: true }
      : false,
    // 静态内容的路径,此路径下的打包文件可在浏览器中访问
    publicPath: config.dev.assetsPublicPath,
    // 接口的代理
    proxy: config.dev.proxyTable,
    // 启用 quiet 后,除了初始启动信息之外的任何内容都不会被打印到控制台
    // 即来自 webpack 的错误或警告在控制台不可见
    quiet: true, // necessary for FriendlyErrorsPlugin
    // 监视文件的选项
    watchOptions: {
      poll: config.dev.poll,
    }
  },
  plugins: [
    // DefinePlugin 允许创建一个在编译时可以配置的全局常量。这里生成了一个当前环境的常量
    new webpack.DefinePlugin({
      'process.env': require('../config/dev.env')
    }),
    // 模块热替换插件,修改模块时不需要刷新页面
    new webpack.HotModuleReplacementPlugin(),
    // 当开启 HMR 的时候使用该插件会显示模块的相对路径
    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
    // 在编译出现错误时,使用 NoEmitOnErrorsPlugin 来跳过输出阶段。这样可以确保输出资源不会包含错误。
    new webpack.NoEmitOnErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      // 打包后 js 文件放在 body 的最后
      inject: true
    }),
    // copy custom static assets
    // 将 static 的内容拷贝到开发路径,忽略这个文件夹下 .XX 的文件
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.dev.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})
 
// webpack 将运行由配置文件导出的函数,并且等待 promise 返回,便于需要异步地加载所需的配置变量。
module.exports = new Promise((resolve, reject) => {
  portfinder.basePort = process.env.PORT || config.dev.port
  portfinder.getPort((err, port) => {
    if (err) {
      reject(err)
    } else {
      // publish the new Port, necessary for e2e tests
      process.env.PORT = port
      // add port to devServer config
      devWebpackConfig.devServer.port = port
 
      // Add FriendlyErrorsPlugin
      // 出错友好处理插件
      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
        compilationSuccessInfo: {
          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
        },
        onErrors: config.dev.notifyOnErrors
        ? utils.createNotifierCallback()
        : undefined
      }))
 
      resolve(devWebpackConfig)
    }
  })
})
1.1.1.7 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')
// 导入 copy-webpack-plugin 用来复制
const CopyWebpackPlugin = require('copy-webpack-plugin')
// 导入 html-webpack-plugin 用来自动生成 html
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 导入 extract-text-webpack-plugin 用来抽离 css 防止 css打包压缩到 js 中
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// 导入 optimize-css-assets-webpack-plugin 用来压缩单独的 css 文件
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
// 导入 uglifyjs-webpack-plugin 用来压缩
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
 
// 区分环境
const env = process.env.NODE_ENV === 'testing'
  ? require('../config/test.env')
  : require('../config/prod.env')
 
const webpackConfig = merge(baseWebpackConfig, {
  module: {
    // 配置独立的css文件的解析规则
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      // 生成独立的文件
      extract: true,
      usePostCSS: true
    })
  },
  // 开发工具 用来调试
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    // 打包后的文件放在 dist 目录下面
    path: config.build.assetsRoot,
    // 编译生成的 js 文件存放在根目录下的 js 目录下,如果 js 文件夹不存在就自动创建
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    // 用来打包 require.ensure 方法中引入的模块,如果该方法中没有引入任何模块,就不会生成 chunk 文件
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    // 自定义一个 plugin 生成当前环境下的一个变量
    new webpack.DefinePlugin({
      'process.env': env
    }),
    // 压缩
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          // 禁止压缩警告信息
          warnings: false
        }
      },
      // 是否开启 sourceMap 用来调试
      sourceMap: config.build.productionSourceMap,
      // 在系统的 CPU 有多于一个内核时自动启用 仅作用于生产构建
      parallel: true
    }),
 
    // 独立的 css 文件夹插件
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      allChunks: true,
    }),
 
    // 压缩 css
    new OptimizeCSSPlugin({
      cssProcessorOptions: config.build.productionSourceMap
        ? { safe: true, map: { inline: false } }
        : { safe: true }
    }),
 
    new HtmlWebpackPlugin({
      filename: process.env.NODE_ENV === 'testing'
        ? 'index.html'
        : config.build.index,
      template: 'index.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'
    }),
    // keep module.id stable when vendor modules does not change
    // 将各模块的 id 变成 hash 值
    new webpack.HashedModuleIdsPlugin(),
    // enable scope hoisting
    new webpack.optimize.ModuleConcatenationPlugin(),
    // split vendor js into its own file
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks (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
    }),
    
    // 抽取第三方库到 app.j
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),
 
    // copy custom static assets
    // 复制自定义的静态资源到生产环境
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})
 
// 压缩
if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')
 
  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      // 目标资源名称 [path]会被替换成原始资源的路径 [query]会被替换成查询字符串
      asset: '[path].gz[query]',
      // 按照zlib的算法
      algorithm: 'gzip',
      // 所有匹配该正则的资源都会被处理 默认值是全部资源
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      // 只有大小大于该值得资源会被处理,单位是 bytes
      threshold: 10240,
      // 压缩率小于这个值得资源才会被处理 默认值是 .8
      minRatio: 0.8
    })
  )
}
 
 
// 打包文件分析工具
if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
 
// 导出
module.exports = webpackConfig
1.1.2 config 目录
目录/文件 说明
dev.env.js 开发环境配置
index.js 主要配置
prod.env.js 生产环境配置
test.env.js 测试环境配置
1.1.2.1 dev.env.js
'use strict'
// 配置文件合并模块
const merge = require('webpack-merge')
// 导入 prod.env.js 配置文件
const prodEnv = require('./prod.env')
 
module.exports = merge(prodEnv, {
  // 导出一个对象,NODE_ENV 是一个环境变量,指定 development 环境
  NODE_ENV: '"development"'
})
1.1.2.2 index.js
'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.
// 引入 node.js 的路径模块
const path = require('path')
 
module.exports = {
  dev: {
 
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    // 建一个虚拟 api 服务器用来代理本机的请求,只能用于开发模式
    proxyTable: {},
 
    // 服务 host
    host: 'localhost',
    // 服务端口号
    port: 8080,
    // 自动打开浏览器浏览器
    autoOpenBrowser: false,
    errorOverlay: true,
    notifyOnErrors: true,
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
 
    // Use Eslint Loader?
    // If true, your code will be linted during bundling and
    // linting errors and warnings will be shown in the console.
    useEslint: true,
    // If true, eslint errors and warnings will also be shown in the error overlay
    // in the browser.
    showEslintErrorsInOverlay: false,
 
    /**
     * Source Maps
     */
 
    // https://webpack.js.org/configuration/devtool/#development
    devtool: 'cheap-module-eval-source-map',
 
    // If you have problems debugging vue-files in devtools,
    // set this to false - it *may* help
    // https://vue-loader.vuejs.org/en/options.html#cachebusting
    cacheBusting: true,
 
    cssSourceMap: true
  },
 
  build: {
    // 下面是相对路径的拼接,假如当前跟目录是 config,那么下面配置的 index 属性的属性值就是 dist/index.html
    index: path.resolve(__dirname, '../dist/index.html'),
 
    // 定义的是静态资源的根目录 也就是 dist 目录
    assetsRoot: path.resolve(__dirname, '../dist'),
    // 定义的是静态资源根目录的子目录 static,也就是 dist 目录下面的 static
    assetsSubDirectory: 'static',
    // 定义的是静态资源的公开路径,也就是真正的引用路径
    assetsPublicPath: '/',
 
    /**
     * Source Maps
     */
    productionSourceMap: true,
    // https://webpack.js.org/configuration/devtool/#production
    devtool: '#source-map',
 
    // 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
    // 是否压缩代码
    productionGzip: false,
    // 定义要压缩哪些类型的文件
    productionGzipExtensions: ['js', 'css'],
 
    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    // 编译完成后的报告,可以通过设置值为 true 和 false 来开启或关闭
    bundleAnalyzerReport: process.env.npm_config_report
  }
}
1.1.2.3 prod.env.js
'use strict'
module.exports = {
  // 导出一个对象,NODE_ENV 是一个环境变量,指定 production 环境
  NODE_ENV: '"production"'
}
1.1.2.4 test.env.js
'use strict'
// 配置文件合并模块
const merge = require('webpack-merge')
// 导入 dev.env.js 配置文件
const devEnv = require('./dev.env')
 
module.exports = merge(devEnv, {
  // 导出一个对象,NODE_ENV 是一个环境变量,指定 testing 环境
  NODE_ENV: '"testing"'
})

依赖文件

看 package.json 比如:

{
  "name": "20200409",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": { // 可以从这里看出用了什么技术
    "axios": "^0.19.0",
    "core-js": "^3.4.3",
    "less": "^3.10.3",
    "less-loader": "^5.0.0",
    "qs": "^6.9.1",
    "swiper": "^5.2.1",
    "vue": "^2.6.10",
    "vue-router": "^3.1.3",
    "vuex": "^3.1.2"
  },
  "devDependencies": { // 能看到使用的工具
    "@vue/cli-plugin-babel": "^4.1.0",
    "@vue/cli-plugin-eslint": "^4.1.0",
    "@vue/cli-service": "^4.1.0",
    "babel-eslint": "^10.0.3",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.0.0",
    "vue-template-compiler": "^2.6.10"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "rules": {},
    "parserOptions": {
      "parser": "babel-eslint"
    }
  },
  "browserslist": [
    "> 1%",
    "last 2 versions"
  ]
}

看完以上的,大致用的功能已经知道了,那么开始从入口文件开始查看。

全局配置

有时候你需要看一下项目的全局配置,来帮助理解业务流程。常用的一些配置,这些配置一定是整个项目中经常出现的部分才会用到,一般在 main.js 中出现如下:

  • 全局指令
  • hasRole 按钮权限等 hasPermission 角色权限
  • 全局方法
  • 下载时间日期等
  • 全局组件
  • 分页上传图片预览标签等
// 部署生产环境和开发环境下的URL。
// 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
// 例如 https://www.xxx.xxx/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.xxx.xxx/admin/,则设置 baseUrl 为 /admin/。
publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
// 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
outputDir: 'dist',
// 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
assetsDir: 'static',
// 是否开启eslint保存检测,有效值:ture | false | 'error'
lintOnSave: process.env.NODE_ENV === 'development',
// 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
productionSourceMap: false,

main.js

main.js 基本形式就像下面这样,所有的配置都是从这里查找。

  • 有的是直接在main.js中可以看;
  • 有的是引用,通过引用再去里面看,比如 store 数据共享,router 路由配置都是在里面;
import { createApp } from 'vue'
import Cookies from 'js-cookie'
import ElementPlus from 'element-plus'
import locale from 'element-plus/lib/locale/lang/zh-cn' // 中文语言
import '@/assets/styles/index.scss' // global css
import App from './App'
import store from './store'
import router from './router'
import directive from './directive' // directive
// 注册指令
import plugins from './plugins' // plugins
import { download } from '@/utils/request'
// svg图标
import 'virtual:svg-icons-register'
import SvgIcon from '@/components/SvgIcon'
import elementIcons from '@/components/SvgIcon/svgicon'
/**
* permission这个是权限 , 一搬会做一些请求或打开页面前的操作,如身份验证,用户登录有效期等
*/
import './permission' // permission control
import { useDict } from '@/utils/dict'
import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel } from '@/utils/ruoyi'
// 分页组件
import Pagination from '@/components/Pagination'
// 自定义表格工具组件
import RightToolbar from '@/components/RightToolbar'
// 文件上传组件
import FileUpload from "@/components/FileUpload"
// 图片上传组件
import ImageUpload from "@/components/ImageUpload"
// 图片预览组件
import ImagePreview from "@/components/ImagePreview"
// 自定义树选择组件
import TreeSelect from '@/components/TreeSelect'
// 字典标签组件
import DictTag from '@/components/DictTag'
const app = createApp(App)
// 全局方法挂载
app.config.globalProperties.useDict = useDict
app.config.globalProperties.download = download
app.config.globalProperties.parseTime = parseTime
app.config.globalProperties.resetForm = resetForm
app.config.globalProperties.handleTree = handleTree
app.config.globalProperties.addDateRange = addDateRange
app.config.globalProperties.selectDictLabel = selectDictLabel
// 全局组件挂载
app.component('DictTag', DictTag)
app.component('Pagination', Pagination)
app.component('TreeSelect', TreeSelect)
app.component('FileUpload', FileUpload)
app.component('ImageUpload', ImageUpload)
app.component('ImagePreview', ImagePreview)
app.component('RightToolbar', RightToolbar)
app.use(router)
app.use(store)
app.use(plugins)
app.use(elementIcons)
app.component('svg-icon', SvgIcon)
directive(app)
// 使用element-plus 并且设置全局的大小
app.use(ElementPlus, {
locale: locale,
// 支持 large、default、small
size: Cookies.get('size') || 'default'
})
app.mount('#app')

别名

{
	alias: {
	// 设置路径
	'~': path.resolve(__dirname, './'),
	// 设置别名
	'@': path.resolve(__dirname, './src')
	},
	extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},

统一接口请求

一搬做一些常用的请求,报错,权限认证,取消请求等:

  • 配置
  • 超时请求地址头部信息
  • 请求前处理
  • Authorization重复提交post、get特殊处理失败或异常
  • 响应拦截器
  • 状态码错误信息二进制数据则直接返回401500非200错误或异常

跨域(主要处理请求后端接口时)

proxy: {
    '/dev-api': {
    target: 'http://localhost:8080',
    changeOrigin: true,
    rewrite: (p) => p.replace('/^\/dev-api/', '')
    }
}

权限、路由拦截

NProgress.configure({showSpinner: false})
// 以下是白名单不需要操作
const whiteList = ['/login', '/auth-redirect', '/bind', '/register']
router.beforeEach((to, from, next) => {
    NProgress.start()
    if (getToken()) {
        to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
        /* has token*/
        if (to.path === '/login') {
            next({path: '/'})
            NProgress.done()
        } else {
            if (store.getters.roles.length === 0) {
                // 判断当前用户是否已拉取完user_info信息
                store.dispatch('GetInfo').then(() => {
                    store.dispatch('GenerateRoutes').then(accessRoutes => {
                        // 根据roles权限生成可访问的路由表
                        router.addRoutes(accessRoutes) // 动态添加可访问路由表
                        next({...to, replace: true}) // hack方法 确保addRoutes已完成
                    })
                }).catch(err => {
                    store.dispatch('LogOut').then(() => {
                        Message.error(err)
                        next({path: '/'})
                    })
                })
            } else {
                next()
            }
        }
    } else {
		// 没有token
        if (whiteList.indexOf(to.path) !== -1) {
		   // 在免登录白名单,直接进入
            next()
        } else {
            // 否则全部重定向到登录页
            next('/login?redirect=${to.fullPath}') 
            NProgress.done()
        }
    }
})
router.afterEach(() => {
    NProgress.done()
})

路由配置

  • 404报错401没有权限最外层封装
  • 左侧栏目导航栏目标签
  • 重定向全局匹配

公共组件

常用的一些公共组件,这些组件常用,或某一些页面中要用到;如下:

  • 表单table
  • 动画
  • 时间
  • 图片裁剪编辑器
  • 弹窗抽屉
  • 详情
  • 验证
  • 二维码
  • 上传
  • 加载
  • 图标
  • 打印水印
  • 日志

常用工具

常用的一些功能如下:

  • 右键
  • 缓存
  • 权限
  • 密码
  • 生成字典数据
  • 常用报错或提示表格时间格式化
  • 克隆合并截流防抖
  • 首字母大小验证等

数据配置

数据比较简单就是常有的一些功能如下:

  • modules 模块
  • 部分app 平台信息
  • user 用户信息

其它的和官方说明的基本一样。

最后

项目讲到这里就讲完了,基本就是几项操作就能看到别人完整的配置流程,公共代码,这样就能快速上手操作了。

  1. README.md 帮助文档
  2. package.json 配置
  3. main.js 入口
  4. 全局组件
  5. 全局方法
  6. 全局指令
  7. router 路由
  8. store 数据存储
  9. components 公共组件
  10. utils 常用工具

好了,小白白上路咯!!

你可能感兴趣的:(vue.js,webpack,javascript)