Vue CLI 2.x builds vue, the most complete analysis of the directory

1. Introduction to vue-cli

vue-cli is a scaffold for quickly building vue projects.

2. vue-cli installation and update

After installing nodeJs and cnpm, install vue-cli globally (other projects can use it directly in the future):

cnpm install -g vue-cli

renew:

cnpm update vue-cli

Check whether the installation is successful (there is a version number is successful, V is capitalized)

vue -V

Check the vue-cli version number in the npm registry:

cnpm view vue-cli

3. Use of vue-cli

After installing webpack and vue-cli, you can start building the vue project:

vue init webpack <Project Name>

eg: Right-click Git Base Here (if you are not using git, you can also hold down the shift key and right-click to select "Open command window here", or cmd:cd \project/lfxProject), as shown in the figure:

or

ps: ESLint (a javascript code detection tool), unit tests (unit test), Nightwatch (an e2e user interface testing tool).

4. Project completion

The project structure is as follows:

The function analysis of each file is as follows:

1. build folder:

The structure of the build folder:

(1)build.js

'use strict'
require('./check-versions')() //调用版本检查

process.env.NODE_ENV = 'production' //将环境配置为生产环境
const ora = require('ora') //npm包 loading插件
const rm = require('rimraf') //npm包 用于删除文件
const path = require('path')//npm包 文件路径工具
const chalk = require('chalk')//npm包 在终端输出带颜色的文字
const webpack = require('webpack')//引入webpack.js
const config = require('../config')//引入配置文件
const webpackConfig = require('./webpack.prod.conf')//引入生产环境配置文件
// 在终端显示loading效果,并输出提示
const spinner = ora('building for production...')
spinner.start()
//先递归删除dist文件再生成新文件,避免冗余
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
  if (err) throw err
  webpack(webpackConfig, (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'
    ))
  })
})

ps: require/export is a method of dependency injection of nodeJs (commonJs specification), import/export is ES6 syntax, which is used to import modules. The ES6 syntax used in nodeJs will eventually be converted into babel tool (babel-loader) ES5

(2) check-version.js: Detect the versions of node and npm, and implement version dependencies

'use strict'
const chalk = require('chalk')
const semver = require('semver')//检查版本
const packageConfig = require('../package.json')
const shell = require('shelljs')//shelljs 模块重新包装了 child_process,调用系统命令更加方便

function exec (cmd) {//返回通过child_process模块的新建子进程,执行 Unix 系统命令后转成没有空格的字符串
  return require('child_process').execSync(cmd).toString().trim()
}

const versionRequirements = [
  {
    name: 'node',
    currentVersion: semver.clean(process.version),//使用semver格式化版本
    versionRequirement: packageConfig.engines.node //获取package.json中设置的node版本
  }
]

if (shell.which('npm')) {
  versionRequirements.push({
    name: 'npm',
    currentVersion: exec('npm --version'),// 自动调用npm --version命令,并且把参数返回给exec函数,从而获取纯净的版本号
    versionRequirement: packageConfig.engines.npm
  })
}

module.exports = function () {
  const warnings = []
  for (let i = 0; i < versionRequirements.length; i++) {
    const mod = versionRequirements[i]
    //若版本号不符合package.json文件中指定的版本号,就报错
    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)
  }
}

(3) utils.js: utils means tool and is a file used to process css. This file contains three tool functions:

  • Path to generate static resources

  • Generate ExtractTextPlugin object or loader string

  • Generate style-loader configuration

var path = require('path')// node自带的文件路径工具
var config = require('../config')// 配置文件
var ExtractTextPlugin = require('extract-text-webpack-plugin')// 提取css的插件

/** @method assertsPath  生成静态资源的路径(判断开发环境和生产环境,为config文件中index.js文件中定义assetsSubDirectory)
 * @param  {String}    _path 相对于静态资源文件夹的文件路径
 * @return {String}          静态资源完整路径
 */
exports.assetsPath = function (_path) {
  var assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory
  //nodeJs path提供用于处理文件路径的工具;path.posix提供对路径方法的POSIX(可移植性操作系统接口)特定实现的访问(可跨平台); path.posix.join与path.join一样,不过总是以 posix 兼容的方式交互
  return path.posix.join(assetsSubDirectory, _path)
}

/**@method cssLoaders  生成处理css的loaders配置,使用css-loader和postcssLoader,通过options.usePostCSS属性来判断是否使用postcssLoader中压缩等方法
 * @param  {Object} option = {sourceMap: true,// 是否开启 sourceMapextract: true // 是否提取css}生成配置
 * @return {Object} 处理css的loaders配置对象
 */
exports.cssLoaders = function (options) {
  options = options || {}

  var cssLoader = {
    loader: 'css-loader',
    options: {
      minimize: process.env.NODE_ENV === 'production',
      sourceMap: options.sourceMap
    }
  }
  /**@method generateLoaders  生成 ExtractTextPlugin对象或loader字符串
   * @param  {Array}        loaders loader名称数组
   * @return {String|Object}        ExtractTextPlugin对象或loader字符串
   */
  function generateLoaders (loader, loaderOptions) {
    var loaders = [cssLoader]
    if (loader) {
      loaders.push({   
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }
    // ExtractTextPlugin提取css(当上面的loaders未能正确引入时,使用vue-style-loader)
    if (options.extract) {// 生产环境中,默认为true
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {//返回vue-style-loader连接loaders的最终值
      return ['vue-style-loader'].concat(loaders)
    }
  }

  return {
    css: generateLoaders(),//需要css-loader 和 vue-style-loader
    postcss: generateLoaders(),//需要css-loader、postcssLoader 和 vue-style-loader
    less: generateLoaders('less'),//需要less-loader 和 vue-style-loader
    sass: generateLoaders('sass', { indentedSyntax: true }),//需要sass-loader 和 vue-style-loader
    scss: generateLoaders('sass'),//需要sass-loader 和 vue-style-loader
    stylus: generateLoaders('stylus'),//需要stylus-loader 和 vue-style-loader
    styl: generateLoaders('stylus')//需要stylus-loader 和 vue-style-loader
  }
}
 
/**@method styleLoaders 生成 style-loader的配置
 * @param  {Object}     options 生成配置
 * @return {Array}      style-loader的配置
 */
exports.styleLoaders = function (options) {
  var output = []
  var loaders = exports.cssLoaders(options)
  //将各种css,less,sass等综合在一起得出结果输出output
  for (var extension in loaders) {
    var loader = loaders[extension]
    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }
  return output
}

(4) vue-loader.conf.js: Process the .vue file, parse each language block (template, script, style) in this file, and convert it into a js module available for js.

'use strict'
const utils = require('./utils')
const config = require('../config')
const isProduction = process.env.NODE_ENV === 'production'
//生产环境,提取css样式到单独文件
const sourceMapEnabled = isProduction
  ? config.build.productionSourceMap
  : config.dev.cssSourceMap
module.exports = {
  loaders: utils.cssLoaders({
    sourceMap: sourceMapEnabled,
    extract: isProduction
  }),
  cssSourceMap: sourceMapEnabled,
  cacheBusting: config.dev.cacheBusting,
  //编译时将“引入路径”转换为require调用,使其可由webpack处理
  transformToRequire: {
    video: ['src', 'poster'],
    source: 'src',
    img: 'src',
    image: 'xlink:href'
  }
}

(5) webpack.base.conf.js: common basic configuration file for development, testing, and production environments, configure output environment, configure module resolve and plug-ins, etc.

'use strict'
const path = require('path')// node自带的文件路径工具
const utils = require('./utils')// 工具函数集合
const config = require('../config')// 配置文件
const vueLoaderConfig = require('./vue-loader.conf')// 工具函数集合
/**
 * 获取"绝对路径"
 * @method resolve
 * @param  {String} dir 相对于本文件的路径
 * @return {String}     绝对路径
 */
function resolve(dir) {
  return path.join(__dirname, '..', dir)
}

module.exports = {
  context: path.resolve(__dirname, '../'),
  //入口js文件(默认为单页面所以只有app一个入口)
  entry: {
    app: './src/main.js'
  },
  //配置出口
  output: {
    path: config.build.assetsRoot,//打包编译的根路径(dist)
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath//发布路径
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],// 自动补全的扩展名
    //别名配置
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),// eg:"src/components" => "@/components"
    }
  },
  module: {
    rules: [
      //使用vue-loader将vue文件编译转换为js
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      //通过babel-loader将ES6编译压缩成ES5
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
      },
      //使用url-loader处理(图片、音像、字体),超过10000编译成base64
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          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]')
        }
      }
    ]
  },
  //nodeJs全局变量/模块,防止webpack注入一些nodeJs的东西到vue中
  node: {
    setImmediate: false,
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }
}

(6) webpack.dev.conf.js: webpack configures the entry in the development environment

'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')//webpack-merge实现合并
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')//webpack的提示错误和日志信息的插件
const portfinder = require('portfinder')// 查看空闲端口位置,默认情况下搜索8000这个端口

const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)

const devWebpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
  },
  devtool: config.dev.devtool,//调试模式
  devServer: {
    clientLogLevel: 'warning',
    historyApiFallback: {//使用 HTML5 History API 时, 404 响应替代为 index.html
      rewrites: [
        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
      ],
    },
    hot: true,//热重载
    contentBase: false, // 提供静态文件访问
    compress: true,//压缩
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    open: config.dev.autoOpenBrowser,//npm run dev 时自动打开浏览器
    overlay: config.dev.errorOverlay
      ? { warnings: false, errors: true }
      : false,// 显示warning 和 error 信息
    publicPath: config.dev.assetsPublicPath,
    proxy: config.dev.proxyTable,//api代理
    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(), // HMR shows correct file names in console on update.
    new webpack.NoEmitOnErrorsPlugin(),//webpack编译错误的时候,中断打包进程,防止错误代码打包到文件中
    // 将打包编译好的代码插入index.html
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
    // 提取static assets 中css 复制到dist/static文件
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.dev.assetsSubDirectory,
        ignore: ['.*']//忽略.*的文件
      }
    ])
  ]
})

module.exports = new Promise((resolve, reject) => {
  portfinder.basePort = process.env.PORT || config.dev.port
  portfinder.getPort((err, port) => { //查找端口号
    if (err) {
      reject(err)
    } else {
      //端口被占用时就重新设置evn和devServer的端口
      process.env.PORT = port
      devWebpackConfig.devServer.port = port
      // npm run dev成功的友情提示
      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)
    }
  })
})

(7) webpack.dev.prod.js: webpack configures the entry in the production environment

'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 UglifyJsPlugin = require('uglifyjs-webpack-plugin')

const env = require('../config/prod.env')

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true,
      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 UglifyJsPlugin({//压缩js
      uglifyOptions: {
        compress: {
          warnings: false
        }
      },
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    new ExtractTextPlugin({//提取静态文件,减少请求
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      allChunks: true,
    }),
    new OptimizeCSSPlugin({//提取优化压缩后(删除来自不同组件的冗余代码)的css
      cssProcessorOptions: config.build.productionSourceMap
        ? { safe: true, map: { inline: false } }
        : { safe: true }
    }),
    new HtmlWebpackPlugin({ //html打包压缩到index.html
      filename: config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,//删除注释
        collapseWhitespace: true,//删除空格
        removeAttributeQuotes: true//删除属性的引号
      },
      chunksSortMode: 'dependency'//模块排序,按照我们需要的顺序排序
    }),

    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new webpack.optimize.CommonsChunkPlugin({   // node_modules中的任何所需模块都提取到vendor
      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
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),
    new CopyWebpackPlugin([//复制static中的静态资源(默认到dist里面)
      {
        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({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

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

module.exports = webpackConfig

2. config folder:

The structure of the config folder:

(1) dev.env.js and prod.env.js: respectively configure: development environment and production environment. This can be configured according to the company's business combined with the back-end requirements to distinguish the attributes of the development environment and the test environment

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

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"'
})

 ps: webpack-merge is used to implement merging similar to ES6's Object.assign()

'use strict'
module.exports = {
  NODE_ENV: '"production"'
}

(*Note that the attribute value should be double-enclosed with "''"), when accessing (getting the value), use it directly:

process.env.property name

ps: process (process) is a global variable of nodejs, and the process.env property returns an object of user environment information

(2) index.js configuration analysis:

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

module.exports = {

  // ===================开发环境配置

  dev: {
    assetsSubDirectory: 'static',//静态资源文件夹(一般存放css、js、image等文件)
    assetsPublicPath: '/',//根目录
    proxyTable: {},//配置API代理,可利用该属性解决跨域的问题
    host: 'localhost', // 可以被 process.env.HOST 覆盖
    port: 3030, // 可以被 process.env.PORT 覆盖
    autoOpenBrowser: true,//编译后自动打开浏览器页面 http://localhost:3030/("port + host",默认"false"),设置路由重定向自动打开您的默认页面
    errorOverlay: true,//浏览器错误提示
    notifyOnErrors: true,//跨平台错误提示
    poll: false, //webpack提供的使用文件系统(file system)获取文件改动的通知devServer.watchOptions(监控文件改动)
    devtool: 'cheap-module-eval-source-map',//webpack提供的用来调试的模式,有多个不同值代表不同的调试模式
    cacheBusting: true,// 配合devtool的配置,当给文件名插入新的hash导致清除缓存时是否生成source-map
    cssSourceMap: true //记录代码压缩前的位置信息,当产生错误时直接定位到未压缩前的位置,方便调试
  },

// ========================生产环境配置

  build: {
    index: path.resolve(__dirname, '../dist/index.html'),//编译后"首页面"生成的绝对路径和名字
    assetsRoot: path.resolve(__dirname, '../dist'),//打包编译的根路径(默认dist,存放打包压缩后的代码)
    assetsSubDirectory: 'static',//静态资源文件夹(一般存放css、js、image等文件)
    assetsPublicPath: '/',//发布的根目录(dist文件夹所在路径)
    productionSourceMap: true,//是否开启source-map
    devtool: '#source-map',//(详细参见:https://webpack.docschina.org/configuration/devtool)
    productionGzip: false,//是否压缩
    productionGzipExtensions: ['js', 'css'],//unit的gzip命令用来压缩文件(gzip模式下需要压缩的文件的扩展名有js和css)
    bundleAnalyzerReport: process.env.npm_config_report //是否开启打包后的分析报告
  }
};

3. The node_modules folder:

The folder for storing the npm installation package generated according to the package.json configuration during npm install

4. src folder:

We need to develop code in the src folder. When packaging, webpack will package and compress the src to the dist folder according to the rules in the build (the build rules depend on the configuration in the config) and run in the browser

(1) Assets file : used to store static resources (css, image), when assets are packaged, the path will be compiled by file-loader in webpack (so assets need to use absolute paths) into js

(2) components folder : used to store .vue components (to achieve reuse and other functions, such as: filters, list items, etc.)

(3) router folder : configure page routing in the router/index.js file

(4) App.vue : It is the main component of the entire project. All pages are switched under App.vue by using the <router-view/> open entry (all routes are sub-components of App.vue)

(5) main.js : entry js file (global js, you can here: initialize vue instance, require/import required plugins, inject router routes, introduce store state management)

5. Static folder:

Webpack defaults to the folder where static resources (css, image) are stored. The difference from assets is that static will directly copy a folder of the same name to the dist folder when packaging (it will not be compiled, and a relative path can be used)

6. Other documents:

(1). .babelrc : Compatible configuration for browser parsing. This file is mainly used to configure presets and plugins. Therefore, different translators have different configuration items, which can be roughly divided into: syntax escape loader, patch escaper, sx and flow plugins

(2) .editorconfig : used to configure the code format (used with code inspection tools, such as: ESLint, the code style can be unified during team development). The code specification rules configured here have higher priority than the editor's default code formatting rules.

( 3) .gitignore : configure the files that need to be ignored when git commits

(4) postcssrc.js : autoprefixer (autocomplete browser prefix for CSS styles); postcss-import (@import import syntax), CSS Modules (specify style scope)

(5) index.html : the entry page of the project, all code will be inserted here after compilation

( 6) package.json : npm configuration file (npm install downloads the corresponding version of the installation package according to package.json)

(7) package.lock.json : lock the version number of each package during npm install (installation)

(8) README.md : Instructions for use of the project

5. Running the project

Open the project in webStorm, first right-click on the Project to perform the following operations (otherwise it will be stuck, and there are various other methods, see: https://www.cnblogs.com/chengwb/p/6183440.html):

1. Start the installation: cnpm install

2. Then npm run dev: run it~

3. Generate the package file: npm run build 

Then you will find that the project has an additional dist folder (for deploying to the production environment, it is the src folder after packaging and compression)

To understand vue cli 3, see my blog: https://my.oschina.net/u/4203303/blog/3144340

To understand node and npm installation/update/use, see my blog: https://my.oschina.net/u/4203303/blog/3144338

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324146774&siteId=291194637