8

vue-cli 入门

如果你现在正在使用Vue.js,当你构建一个原型的时候,你所需要做的通常就是通过<script>把Vue.js引入进来,然后就完事了。但是真实情况往往不是这样的。当我们真正开发一个应用的时候,我们不可避免的会用到一大堆的工具,模块化、预处理器、热模块加载、代码校验和测试。这些工具对于一个需要长期维护的大型应用是必须的,但是项目初始化将会是让人痛苦的事情。这就是为什么我们做了vue-cli,让一个简单的命令行工具来帮助你快速的构建一个拥有强大构建能力的Vue.js项目。

安装运行

# 安装vue-cli,npm全局安装
npm install -g vue-cli
# 使用vue-cli初始化项目
vue init webpack my-project-vue

这个命令会从https://github.com/vuejs-temp...获取webpack的模板,并且放到my-project-vue下
详细参考:[](https://github.com/vuejs/vue-...

执行过程如下:

vue init webpack my-project-vue

  This will install Vue 2.x version of the template.

  For Vue 1.x use: vue init webpack#1.0 my-project-vue

# 这些都是提示可选的,按需要选择即可,都是一些很有名的js工具,因为测试学习,我全选了
? Project name my-project-vue 
? Project description A Vue.js project
? Author yuanyuanyuan
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Setup unit tests with Karma + Mocha? Yes
? Setup e2e tests with Nightwatch? Yes

   vue-cli · Generated "my-project-vue".

   To get started:
   
     cd my-project-vue
     npm install
     npm run dev
   
   Documentation can be found at https://vuejs-templates.github.io/webpack

vuejs-templates/webpack下载和配置好之后,就需要使用npm将相关的依赖和模块进行下载安装

# 进入到目录
cd my-project-vue

ls
README.md    config       package.json static
build        index.html   src          test

因为vue 初始化的时候也把package.json生成了,npm 可以通过这个json进行模块和依赖安装

# 安装依赖
npm install  //安装package.json里的所有模块依赖

觉得慢,可以修改淘宝源:npm config set registry https://registry.npm.taobao.org

# 开始运行
npm run dev

DONE  Compiled successfully in 6456ms

> Listening at http://localhost:8080

可能会遇到缺少包导致打开页面出现404错误,可以根据包提示安装包
sudo npm install --save strip-ansi ansi-html html-entities
-save和save-dev可以直接将包添加到package.json文件中

至此完成安装并运行,并且可以在浏览器查看到效果:

Welcome to Your Vue.js App

理解原理

图片描述

本文档主要使用vue-webpack-boilerplate模板来理解,根据官方解释:

webpack - A full-featured Webpack + vue-loader setup with hot reload, linting, testing & css extraction.
全功能的webpack + vue-loader模板,并且配置好热更新等功能

npm run dev 这个命令会执行一个dev服务器本地监听,可以热更新

npm run dev: first-in-class development experience.

* Webpack + vue-loader for single file Vue components.
* State preserving hot-reload
* State preserving compilation error overlay
* Lint-on-save with ESLint
* Source maps

npm run build 会创建生产环境的配置,会生成合并的文件

npm run build: Production ready build.

* JavaScript minified with UglifyJS.
* HTML minified with html-minifier.
* CSS across all components extracted into a single file and minified with cssnano.
* All static assets compiled with version hashes for efficient long-term caching, and a production index.html is auto-generated with proper URLs to these generated assets.
* Use npm run build --reportto build with bundle size analytics.

vue-webpack-boilerplate模板的目录架构大致如下:

.
|-- build                            // 项目构建(webpack)相关代码
|   |-- build.js                     // 生产环境构建代码
|   |-- check-version.js             // 检查node、npm等版本
|   |-- dev-client.js                // 热重载相关
|   |-- dev-server.js                // 构建本地服务器
|   |-- utils.js                     // 构建工具相关
|   |-- webpack.base.conf.js         // webpack基础配置
|   |-- webpack.dev.conf.js          // webpack开发环境配置
|   |-- webpack.prod.conf.js         // webpack生产环境配置
|-- config                           // 项目开发环境配置
|   |-- dev.env.js                   // 开发环境变量
|   |-- index.js                     // 项目一些配置变量
|   |-- prod.env.js                  // 生产环境变量
|   |-- test.env.js                  // 测试环境变量
|-- src                              // 源码目录
|   |-- components                     // vue公共组件
|   |-- store                          // vuex的状态管理
|   |-- App.vue                        // 页面入口文件
|   |-- main.js                        // 程序入口文件,加载各种公共组件
|-- static                           // 静态文件,比如一些图片,json数据等
|   |-- data                           |-- .babelrc                         // ES6语法编译配置
|-- .editorconfig                    // 定义代码格式
|-- .gitignore                       // git上传需要忽略的文件格式
|-- README.md                        // 项目说明
|-- favicon.ico 
|-- index.html                       // 入口页面
|-- package.json                     // 项目基本信息,npm的包依赖安装信息
.

package.json(npm配置)

  • 他是项目根目录下的一个文件,定义该项目开发所需要的各种模块以及一些项目配置信息(如项目名称、版本、描述、作者等)

  • npm init初始化的时候就会生成这个文件,然后如果需要自定义就可以在里面编辑

{
  "name": "my-project-vue",
  "version": "1.0.0",
  "description": "A Vue.js project",
  "author": "XXXXX",
  "private": true,
  "scripts": { //来指定npm相关命令和通知npm执行这些命令
    "dev": "node build/dev-server.js", //根据不同的环境执行不同的文件,例如在开发环境下,在命令行中运行npm run dev就相当于在执行node build/dev-server.js
    "build": "node build/build.js",
    "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
    "e2e": "node test/e2e/runner.js",
    "test": "npm run unit && npm run e2e",
    "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
  },
  "dependencies": { //指定了项目运行时所依赖的模块
    "ansi-html": "0.0.7",
    "html-entities": "^1.2.0",
    "strip-ansi": "^3.0.1",
    "vue": "^2.1.10", //有vue
    "vue-router": "^2.2.0" //有vue-router
  },
  "devDependencies": { //指定了项目开发时所依赖的模块
    "autoprefixer": "^6.7.2",
    "babel-core": "^6.22.1",
    "babel-eslint": "^7.1.1",
    "babel-loader": "^6.2.10",
    "babel-plugin-istanbul": "^3.1.2",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-preset-es2015": "^6.22.0",
    "babel-preset-stage-2": "^6.22.0",
    "babel-register": "^6.22.0",
    "chai": "^3.5.0",
    "chalk": "^1.1.3",
    "chromedriver": "^2.27.2",
    "connect-history-api-fallback": "^1.3.0",
    "cross-env": "^3.1.4",
    "cross-spawn": "^5.0.1",
    "css-loader": "^0.26.1",
    "eslint": "^3.14.1",
    "eslint-config-standard": "^6.2.1",
    "eslint-friendly-formatter": "^2.0.7",
    "eslint-loader": "^1.6.1",
    "eslint-plugin-html": "^2.0.0",
    "eslint-plugin-promise": "^3.4.0",
    "eslint-plugin-standard": "^2.0.1",
    "eventsource-polyfill": "^0.9.6",
    "express": "^4.14.1",
    "extract-text-webpack-plugin": "^2.0.0-rc.2",
    "file-loader": "^0.10.0",
    "friendly-errors-webpack-plugin": "^1.1.3",
    "function-bind": "^1.1.0",
    "html-webpack-plugin": "^2.28.0",
    "http-proxy-middleware": "^0.17.3",
    "inject-loader": "^2.0.1",
    "json-loader": "^0.5.4",
    "karma": "^1.4.1",
    "karma-coverage": "^1.1.1",
    "karma-mocha": "^1.3.0",
    "karma-phantomjs-launcher": "^1.0.2",
    "karma-sinon-chai": "^1.2.4",
    "karma-sourcemap-loader": "^0.3.7",
    "karma-spec-reporter": "0.0.26",
    "karma-webpack": "^2.0.2",
    "lolex": "^1.5.2",
    "mocha": "^3.2.0",
    "nightwatch": "^0.9.12",
    "opn": "^4.0.2",
    "ora": "^1.1.0",
    "phantomjs-prebuilt": "^2.1.14",
    "selenium-server": "^3.0.1",
    "semver": "^5.3.0",
    "shelljs": "^0.7.6",
    "sinon": "^1.17.7",
    "sinon-chai": "^2.8.0",
    "url-loader": "^0.5.7", 
    "vue-loader": "^10.3.0",//vue-loader等
    "vue-style-loader": "^2.0.0", 
    "vue-template-compiler": "^2.1.10",
    "webpack": "^2.2.1",
    "webpack-bundle-analyzer": "^2.2.1",
    "webpack-dev-middleware": "^1.10.0",
    "webpack-hot-middleware": "^2.16.1",
    "webpack-merge": "^2.6.1"
  },
  "engines": { //顾名思义,就是告诉npm使用什么版本的node和npm
    "node": ">= 4.0.0",
    "npm": ">= 3.0.0"
  }
}

这个package.json文件其实很长,不过主要关注一些日常使用到的就行了,全部配置在官方:https://docs.npmjs.com/files/package.json

webpack配置

使用npm run dev 就会执行dev-server.js,因为这个在packet.json里面配置了,所以需要知道dev-server.js里面有什么

dev-server.js
// 检查 Node 和 npm 版本(require指定loader)
require('./check-versions')()
// 获取config目录的默认配置,并且会默认指定index.js文件
var config = require('../config')
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}
// 一个可以强制打开浏览器并跳转到指定 url 的插件
var opn = require('opn')
// 使用 NodeJS 自带的文件路径工具
var path = require('path')
var express = require('express')
// 使用 webpack
var webpack = require('webpack')
// http-proxy可以实现转发所有请求代理到后端真实API地址,以实现前后端开发完全分离
// 使用 proxyTable
var proxyMiddleware = require('http-proxy-middleware')
// 判断是否使用 dev 环境的 webpack 配置
var webpackConfig = process.env.NODE_ENV === 'testing'
  ? require('./webpack.prod.conf')
  : require('./webpack.dev.conf')

// default port where dev server listens for incoming traffic
var port = process.env.PORT || config.dev.port
// automatically open browser, if not set will be false
var autoOpenBrowser = !!config.dev.autoOpenBrowser
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
// 使用 config.dev.proxyTable 的配置作为 proxyTable 的代理配置
var proxyTable = config.dev.proxyTable
// 使用 express 启动一个服务
var app = express()
// 启动 webpack 进行编译
var compiler = webpack(webpackConfig)
// 启动 webpack-dev-middleware,将 编译后的文件暂存到内存中
var devMiddleware = require('webpack-dev-middleware')(compiler, {
  publicPath: webpackConfig.output.publicPath,
  quiet: true
})
/ 启动 webpack-hot-middleware,也就是我们常说的 Hot-reload 热加载
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
  log: () => {}
})
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
    hotMiddleware.publish({ action: 'reload' })
    cb()
  })
})

// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
  var options = proxyTable[context]
  if (typeof options === 'string') {
    options = { target: options }
  }
  app.use(proxyMiddleware(options.filter || context, options))
})

// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')())

// serve webpack bundle output
// 将暂存到内存中的 webpack 编译后的文件挂在到 express 服务上
app.use(devMiddleware)

// enable hot-reload and state-preserving
// compilation error display
// 将 Hot-reload 挂在到 express 服务上
app.use(hotMiddleware)

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

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

devMiddleware.waitUntilValid(function () {
  console.log('> Listening at ' + uri + '\n')
})
// 让我们这个 express 服务监听 port 的请求,并且将此服务作为 dev-server.js 的接口暴露
module.exports = app.listen(port, function (err) {
  if (err) {
    console.log(err)
    return
  }

  // when env is testing, don't need open it
  if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
    opn(uri)
  }
})

备注:

  1. 加载器(Loaders) 引用
    loader 是对应用程序中资源文件进行转换。它们是(运行在 Node.js 中的) 函数,可以将资源文件作为参数的来源,然后返回新的资源文件。

例如,你可以使用 loader 告诉 webpack 加载 CSS 文件,或者将 TypeScript 转为 JavaScript。
loader 解析类似于模块。loader 模块需要导出(module.export)一个函数,并且使用兼容 Node.js 的 JavaScript 编写。在通常情况下,你可以使用 npm 管理 loader,但是你也可以在应用程序中将 loader 作为文件使用。

  1. 可以看到webpack其实使用了node.js的express网页服务器来进行处理网页相关的数据,相当于使用一个类似apache这样的web服务器来执行解析html等文件,只是这里换成了node.js的express,并且可以执行js文件

需要注意一点:require的时候,如果没有指定文件的话,有一些情况是会自定指定该目录下的index.js文件的,详情参考

webpack.dev.conf.js

dev-server.js的环境配置是调用webpack.dev.conf.js的,所以也需要看看了解一下

// 使用一些小工具
var utils = require('./utils')
// 使用 webpack
var webpack = require('webpack')
// 获取config目录的默认配置,并且会默认指定index.js文件
var config = require('../config')
// 使用 webpack 配置合并插件
var merge = require('webpack-merge')
// 加载 webpack.base.conf
var baseWebpackConfig = require('./webpack.base.conf')
// 使用 html-webpack-plugin 插件,这个插件可以帮我们自动生成 html 并且注入到 .html 文件中
var HtmlWebpackPlugin = require('html-webpack-plugin')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')

// add hot-reload related code to entry chunks
// 将 Hol-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: {
  // 使用 styleLoaders
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
  },
  // cheap-module-eval-source-map is faster for development
  devtool: '#cheap-module-eval-source-map',
  plugins: [
  // definePlugin 接收字符串插入到代码当中, 所以你需要的话可以写上 JS 的字符串
    new webpack.DefinePlugin({
      'process.env': config.dev.env
    }),
    // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
    // HotModule 插件在页面进行变更的时候只会重回对应的页面模块,不会重绘整个 html 文件
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin
    // 将 index.html 作为入口,注入 html 代码后生成 index.html文件
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
    new FriendlyErrorsPlugin()
  ]
})
webpack.base.conf.js

webpack.dev.conf.js会导入webpack.base.conf.js,并且会进行合并配置

var path = require('path')
var utils = require('./utils')
var config = require('../config')
//加载了vue-loader,主要是我们这个项目是vue-cli构建的,所以相对配置也所特别
var vueLoaderConfig = require('./vue-loader.conf')
var eslintFriendlyFormatter = require('eslint-friendly-formatter')

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

module.exports = {
  entry: { //主要入口
    app: './src/main.js'
  },
  output: {
    path: config.build.assetsRoot,// 编译输出的根路径
    filename: '[name].js', //输出的文件名
    //判断是否dev环境来处理静态资源
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    modules: [
      resolve('src'),
      resolve('node_modules')
    ],
    alias: {
     // 默认路径代理,例如 import Vue from 'vue',会自动到 'vue/dist/vue.common.js'中寻找
      'vue$': 'vue/dist/vue.common.js',
      'src': resolve('src'),
      'assets': resolve('src/assets'),
      'components': resolve('src/components')
    }
  },
  module: {
    rules: [ //对不同文件使用不同的loader
      {
        test: /\.(js|vue)$/,
        loader: 'eslint-loader',
        enforce: "pre",
        include: [resolve('src'), resolve('test')],
        options: {
          formatter: eslintFriendlyFormatter
        }
      },
      { //使用vue-loader解析.vue的文件
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      { //普通的js文件使用babel-loader解析
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test')]
      },
      { //其他差不多类似
        test: /\.json$/,
        loader: 'json-loader'
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        query: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        query: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  }
}
config/index.js
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')

module.exports = {
  build: { //如果是build的话就使用production环境配置
    env: require('./prod.env'),
    index: path.resolve(__dirname, '../dist/index.html'),
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    productionSourceMap: true,
    // 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
    bundleAnalyzerReport: process.env.npm_config_report
  },
  dev: { // dev的话就使用dev的环境配置
    env: require('./dev.env'),
    port: 8080,
    autoOpenBrowser: true,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}

可以看到dev是没有生成index.html之类的文件的,那是因为dev的话会直接在内存处理,方便调试,也可以利用热加载直接更新

参考:

  1. https://github.com/vuejs/vue-cli

  2. https://github.com/vuejs-templates/webpack

  3. https://segmentfault.com/a/1190000007880723

  4. https://gold.xitu.io/post/584e48b2ac502e006c74a120

  5. https://doc.webpack-china.org/configuration/

  6. https://webpack.js.org/configuration/


线上猛如虎
2.2k 声望178 粉丝

你们都有梦想的,是吧.怀抱着梦想并且正朝着梦想努力的人,寻找着梦想的人,我想为这些人加油呐喊!