Vue-Cli3

创建项目

vue cli是一个基于vue.js进行快速开发的完整系统,通常包含三个组件,分别是:

  • cli:@vue/cli是全局安装的NPM包,提供终端vue命令,比如vue createvue servevue ui等命令。
  • cli服务:@vue/cli-service是开发环境以来,构建在webpack和webpack-dev-server之上,提供诸如servebuildinspect等命令。
  • cli插件,为vue项目提供可选功能的NPM包,比如Babel/TypeScript转译、ESLint集成、UNIT和E2E测试等。

安装

  • 安装 vue-cli3 需要 Node.js 8.9+ 版本,推荐Node.js 8.11.0+版本。
  • vue-cli3 的包名由vue-cli改为@vue/cli,如果已安装vue-cli1.x或2.x,可先通过npm uninstall -g vue-cli卸载。
  1. 检查Node.js版本
  2. 卸载Vue-cli2.x
  3. 重新安装@vue/cli
  4. 创建应用
  5. 允许服务
# 查看Node.js版本
$ node -v
v12.14.0

# 卸载vue-cli
$ npm uninstall -g vue-cli

# 安装vue脚手架
$ npm i -g @vue/cli

# 查看vue版本
$ vue --version
2.9.6

# 使用脚手架创建项目
$ vue create chat

$ cd chat

$ npm run serve

浏览器访问 http://127.0.0.1:8080

选择预设

创建Vue项目会提示选取一个preset预设,可选默认包含基本的Babel + ESLint设置的预设,也可手工选择特性。

Vue CLI v4.1.2
? Please pick a preset:
> default(babel, eslint)
 Manually select features
  • 默认预设:default(babel, eslint)

默认设置适合快速创建新项目的原型,无任何辅助功能的NPM包。

  • 手工选择特性:Manually select features

手动配置,使用方向键控制,使用空格键选中,使用a键选择。选项是所需面向生产环境的项目,提供可供选择功能的NPM包。

手动配置提供的NPM包

Vue CLI v4.1.2
? Please pick a preset: Manually select features
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 ( ) Router
 ( ) Vuex
 ( ) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing

VUE CLI使用一套基于插件的架构,打开package.jsoon会发现依赖都是以@vue/cli-plugin-开头。插件可以修改webpack内部配置,也可以向vue-cli-service注入命令。

安装插件

例如:手工添加eslint插件,命令会将@vue/eslint解析为完整的包名@vue/cli-plugin-eslint,然后从NPM安装并调用其生成器。若不带@vue前缀,命令会换做解析一个unscoped包,可基于一个指定的scope使用。

$ vue add @vue/eslint
NPM包 描述
Babel 转码器,将ES6转换为ES5。
TypeScript JS超集扩展了JS语法,需编译输出为JS。
PWA 渐进式Web应用程序
Router Vue路由
Vuex Vue状态管理模式
CSS Pre-processors CSS预处理器
Linter/Formatter 代码风格检查和格式化
Unit Testing 单元测试
E2E Testing e2e测试

babel

Babel是一个JavaScript编译器,用于将ECMAScript2015+版本的代码转换为先后兼容的JavaScript语句。

  • 语法转换
  • 通过Polyfill在目标环境中添加缺失的特性
  • 源码转换

vue-router

vue-router默认采用hash模式,也可选择history模式。vue-router利用浏览器自身的hash模式和history模式的特性来实现前端路由,通过了浏览器提供的接口。

  • hash:浏览器URL地址栏中# 符号,hash不被包括在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。
  • history:利用HTML5 History Interface中新增的pushState()replaceState()方法,需特定浏览器支持,适用于单页客户端应用。history mode徐后台配置支持。

vue cli 3中采用src/router.js文件替代了vue cli 2的src/router/index.js文件

vuex

vuex用于状态管理,vue cli 3中默认使用store.js代替原vue cli 2种store文件夹中的三个JS文件action、mutations、state以及store的getters用法。

css pre-processors

css预处理器用于解决浏览器兼容并简化css代码等问题

目录结构

  • vue-cli3.x相比vue-cli2.x目录简洁了很多,没有了build和config等文件夹。
  • vue-cli3.x相比vue-cli2.x所创建目录中已经看不到webpack的配置
  • vue-cli3.x中创建vue.config.js文件通过configureWebpack来配置webpack
文件 描述
.browserslistrc 用于指定项目的目标浏览器范围
package.json 定义项目所需模块及项目信息
package-lock.json 锁定安装时的包版本号
tsconfig.json TypeScript配置文件
babel.config.js Babel配置文件
.eslintrc.js eslint检测规则配置
.gitignore GIT配置文件
public 配置ico和index.html
src vue项目文件夹
src/assets/ 用于存放项目静态文件,包括图片、JS、SVG等,生产环境下会被WebPack复制。
src/components/ 存放公用Vue组件页面
src/styles 存放重写reset.css以及字体图标CSS文件
src/views 存放较大模块,比如登录页、注册页、首页等。

public

vue cli 3摒弃vue cli2的static文件夹新增了public文件夹,vue cli 2中static文件夹是webpack存放默认静态资源的文件夹,打包时会直接复制一份到dist文件夹中,且不会经过webpack编译。vue cli 3中静态资源有两种处理方式:

  • 经过webpack处理:在JS被导入或在template/css中通过相对路径被引用的资源会编译并压缩。
  • 不经过webpack处理:放置在public文件夹下或通过绝对路径被引用的资源将会直接拷贝一份,且不做任何编译压缩处理。

vue cli 3中public/index.html模板会被html-webpack-plugin处理。

src/views

vue cli 3的src文件夹中新增了views文件夹用于存放页面,用于区分components组件。

环境配置.env

项目中通常包含多种模式,常见比如开发模式development、生产模式production等,开发中会根据环境变量process.env.NODE_ENV进行区分。

在根目录下创建.env.production生产环境配置和.env.development开发环境配置,配置文件以键值对的方式,配置项必须以VUE_APP_以前缀。NODE_ENVBASE_URL是两个特殊变量,在代码中始终可用。

$ vim .env.development
BASE_URL = http://127.0.0.1:8080
VUE_APP_API_URL = http://127.0.0.1:4000
VUE_APP_WX_APPID = wx6fe244f7d2197mc1

加载环境配置文件

vue会根据启动命令启动加载对应的环境文件,这是因为vue是根据文件名进行加载的。

  • npm run serve 运行服务会自动加载 .env.development 开发环境配置文件
  • npm run build 运行构建会自动加载 .env.production 生产环境配置文件

运行npm run serve启动服务命令时,若在vue.config.js核心中配置了devServer开发服务器选项的proxy代理设置后,开发环境中会使用proxy代理服务器访问接口数据。但正式生产环境下此选项会失效。因为此时vue会读取.env.production环境配置,而忽略vue.config.js中的devServer选项。

环境配置自定义

通过在package.jsonscripts配置项中添加 --mode xxx来选择不同环境。

$ vim package.json
"scripts": {
  "serve": "vue-cli-service serve",
  "build": "vue-cli-service build",
  "test:unit": "vue-cli-service test:unit",
  "lint": "vue-cli-service lint"
}
"scripts": {
  "serve": "vue-cli-service serve --mode development",
  "build": "vue-cli-service build --mode development",
  "test:unit": "vue-cli-service test:unit --mode development",
  "lint": "vue-cli-service lint --mode development"
},

项目配置vue.config.js

vue.config.js是一个可选的配置文件,若项目根目录中存在此文件,则会被@vue/cli-service自动加载。

vue-cli 3.x 创建的目录下看不到WebPack配置。手工配置webpack可在根目录下创建vue.config.js文件。在根目录下创建vue.config.js文件作为Vue项目配置文件,其中配置输出路径名、根目录、预处理、devServer配置、PWA、DLL、第三方插件等。

$ vim vue.config.js
module.exports = {
    configureWebpack:config=>{
        if(process.env.NODE_ENV === "production"){

        }else{
            
        }

    }
};

配置选项

选项 描述
publicPath 部署应用宝的基本URL,和webpack的output.publicPath一致。
outputDir 运行vue-cli-service build时生成的生产环境构建文件的目录,目标目录在够坚强会被清除。
assetsDir 静态资源目录
indexPath 指定生成的index.html的输出路径
filenameHashing 是否在生成的静态资源文件名中包含hash以控制缓存
pages 在多页模式下构建应用,每个页面对应一个入口文件。
lintOnSave 是否在开发环境下通过eslint-loader在每次保存时lint代码
runtimeCompiler 是否使用包含运行时编译器的vue构建版本

CSS Pre-processors

VUE CLI支持CSS Modules、PostCSS和SASS、LESS、Stylus在内的预处理器。所有编译后的CSS会通过 css-loader 来解析其中的url()引用,并将应用作为模块请求来处理。这意味着可以根据本地文件结构使用相对路径引用静态资源。若要应用NPM以来中的文件,或使用webpack alias,则需在路径前添加 ~ 前缀来避免歧义。

TypeScript

安装TypeScript

$ npm i -S typescript
$ npm i -S @vue/cli-plugin-typescript

配置TypeScript

若目录下存在tsconfig.json文件则意味着该目录是TypeScript项目的根目录,tsconfig.json文件中指定了用来编译项目的根文件和编译选项。项目可使用tsconfig.json来编译。

  • 当不带任何输出文件的情况下调用tsc文件,编译器会从当前目录开始查找tsconfig.json文件,并逐级向上搜索父级目录。
  • 当不带任何输出文件时调用tsc文件且使用命令行参数--project-p指定一个包含tsconfig.json文件的目录。
  • 当在命令行上指定了输入文件时,tsconfig.json文件会被忽略。

根目录下创建tsconfig.json,默认情况下typescript只负责静态检查,即使遇到错误也仅仅在编译时报错并不会中断编译,最终还是会生成一份JS文件,如果想要在报错时终止JS文件的生成,可在tsconfig.json配置中设置noEmitOnError选项为true

$ vim tsconfig.json
{
  //编译选项
  "compilerOptions": {
    //编译目标版本
    "target": "esnext",
    //指定模块系统
    "module": "esnext",
    //是否启用严格类型检查
    "strict": true,
    //在.tsx文件中支持JSX
    "jsx": "preserve",
    //从tslib导入辅助工具函数
    "importHelpers": true,
    //模块处理方式,默认classic
    "moduleResolution": "node",
    //是否启用实验性的ES装饰器
    "experimentalDecorators": true,
    //报错时不生成输出文件
    "noEmitOnError":true,
    //通过为所有导入创建名称空间对象,支持CommonJS和ES模块之间的互操作性。意味着allowSyntheticDefaultImports。
    "esModuleInterop": true,
    //是否允许从没有设置默认导出的模块中默认导入,仅用于类型检查。
    "allowSyntheticDefaultImports": true,
    //是否生成map文件
    "sourceMap": true,
    //工作根目录
    "baseUrl": ".",
    //指定引入的类型声明文件,默认自动引入所有声明文件,一旦指定则会禁用自动引入,只引入指定的类型。
    "types": [
      "webpack-env",
      "mocha",
      "chai"
    ],
    //指定模块的路径,和baseUrl有关联,和webpack中resolve.alias配置一样。
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    //编译过程中需引入的库文件列表
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  //指定匹配列表,属于自动指定该路径下所有TS相关文件。
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  //指定排除列表,include的反向操作。
  "exclude": [
    "node_modules"
  ]
}

错误处理

$ npm run serve

> sxyh_web_stats@0.1.0 serve D:\vue\workspace\sxyh_web_stats
> vue-cli-service serve

 INFO  Starting development server...
 ERROR  WebpackOptionsValidationError: Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
 - configuration.entry[2] should be a string.
   -> A non-empty string
WebpackOptionsValidationError: Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
 - configuration.entry[2] should be a string.
   -> A non-empty string

例如:针对多页应用的项目配置

$ vim vue.config.js
const path = require("path");
const fs = require("fs");
// const glob = require("glob");
const pxtoviewport = require("postcss-px-to-viewport");

//是否开发调试模式
const debug = process.env.NODE_ENV === "development" ? true : false;

//获取文件路径
const joinPath = (...args)=>path.join(__dirname, ...args);

/*获取配置*/
const config = (filename,field="")=>{
    let value = require(joinPath("config", filename));
    if(field!==""){
        value = value[field];
    }
    return value;
};

/*获取多页面配置选项*/
const getPages = ()=>{
    let pages = {};
    //获取pages目录下所有文件夹,即每个单页。
    fs.readdirSync(joinPath("src", "pages")).forEach(dirname=>{
        //生成应用组件文件
        const app  = joinPath("src", "pages", dirname, "App.vue");
        if(!fs.existsSync(app)){
            //todo 读取模板内容 替换内容后写入
            let code = `
<template>
    <router-view/>
</template>

<script>
    export default {
        name:"app"
    }
</script>

<style scoped>

</style>
`;
            fs.writeFileSync(app, code);
        }
        //生成路由文件
        const router = joinPath("src", "pages", dirname, "router.js");
        if(!fs.existsSync(router)){
            let code = `
import Vue from "vue";
import Router from "vue-router";

Vue.use(Router);

export default new Router({routes:[

]});
`;
            fs.writeFileSync(router, code);
        }
        //生成入口文件
        const entry = joinPath("src", "pages", dirname, "main.js");
        if(!fs.existsSync(entry)){
            let code = `
import Vue from "vue";
import Axios from "axios";

import App from "./App.vue";
import router from "./router.js";

Vue.config.productionTip = false;
Vue.prototype.axios = Axios;

new Vue({render:h=>h(App), router}).$mount("#${dirname}");
`;
            fs.writeFileSync(entry, code);
        }
        //单页配置选项
        const template = "index.html";
        const filename = `${dirname}.html`;
        const chunks = ['chunk-vendors', 'chunk-common', dirname];
        const chunksSortMode = "manual";
        const minify = false;
        const inject = true;
        //自定义页面数据
        const pageData = config("page", dirname) || {};
        if(pageData.title === undefined){
            Object.assign(pageData, {title:dirname});
        }
        if(pageData.idname === undefined){
            Object.assign(pageData, {idname:dirname});
        }
        pages[dirname] = {entry, template, filename, pageData, chunks, chunksSortMode, minify, inject};
    });
    return pages;
};


module.exports = {
    publicPath:debug?"/":"",
    outputDir:"dist",
    assetsDir:"assets",
    filenameHashing:true,
    lintOnSave:!debug,
    runtimeCompiler:!debug,
    pages:getPages(),
    configureWebpack:config=>{
        const extensions = [".js", ".json", ".vue", ".css"];
        const alias = {
            "@":path.join(__dirname, "src"),
            "src":path.join(__dirname, "../src"),
            "assets":path.join(__dirname, "../src/assets"),
            "components":path.join(__dirname, "../src/components")
        };
        config.resolve = {extensions, alias};
    },
    css:{
        loaderOptions:{
            postcss:{
                plugins:[
                    pxtoviewport({
                        unitToConvert:"px",
                        unitPrecision:3,
                        viewportWidth:750,
                        viewportUnit:"vw",
                        fontViewportUnit:"vw",
                        minPixelValue:1,
                        mediaQuery:false,
                        replace:true,
                        propList:["*"],
                        selectorBlackList:[],
                        exclude:/(\/|\\)(node_modules)(\/|\\)/,
                        landscape:false,
                        landscapeUnit:"vh",
                        landscapeWidth:1334
                    })
                ]
            }
        }
    },
    //开发服务器
    devServer:{
        //设置代理
        proxy:{
            "/api":{
                target:"http://127.0.0.1:4000",
                ws:false,
                changeOrigin:true
            }
        }
    }
};

启动入口

Vue2实例启动入口文件默认为main.js

$ vim src/pages/index/main.js
import Vue from 'vue';
import Axios  from "axios";

import App from './App.vue';
import router from "./router.js";

Vue.config.productionTip = false;

Vue.prototype.axios = Axios;
Vue.prototype.apiUrl = process.env.VUE_APP_API_URL;
Vue.prototype.debug = process.env.NODE_ENV === "development";


//创建vue应用实例并挂在到#index元素上
const vm = new Vue({render:h=>h(App), router:router});//未挂载状态
//手工挂载vm实例
vm.$mount('#index');

这里运用了Vue2新增的Render方法,为了得到更好地运行速度,Vue2也采用了Virtual DOM虚拟DOM技术。Virtual DOM是一种比浏览器原生DOM性能更好的虚拟组件模型。

const vm = new Vue({render:h=>h(App), router:router});//未挂载状态

通过import将Vue.js文件引入后创建Vue实例对象,在Vue实例中使用Render方法来绘制App这个Vue组件以完成初始化。

vm.$mount('#index');//手工挂载vm实例

将Vue实例绑定到页面中ID为index的元素上,这样App Vue程序就引导成功了。

一个Vue实例必须与一个页面元素绑定,Vue实例一般用作Vue的全局配置来使用。

$ vim public/index.html
<% const page = htmlWebpackPlugin.options.pageData; %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <!-- 在 head 标签中添加 meta 标签,并设置 viewport-fit=cover 值 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
    <!-- 开启 safe-area-inset-bottom 属性 -->
    <van-number-keyboard safe-area-inset-bottom />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <meta http-equiv="Cache-Control" content="no-cache" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
    <title><%= page.title %></title>
    <style>
        body{
            margin: 0;
            overflow-x: auto;
            color: #323233;
            font-size: 16px;
            font-family: PingFang SC, 'Helvetica Neue', Arial, sans-serif;
            background-color: #f7f8fa;
            -webkit-font-smoothing: antialiased;
        }
    </style>
</head>
<body>
<noscript>
    <strong>很抱歉,如果没有启用javascript,vue-cli3无法正常工作。请启用它以继续。</strong>
</noscript>
<div id="app">
    <div id="<%= page.idname %>"></div>
</div>
</body>
</html>

单页组件

Vue2默认Vue组件文件为App.vue*.vue是Vue特色的文件格式表示是一个Vue组件,是Vue特色又被称为单页式组件。*.vue文件同时承载视图模板、样式定义和组件代码。

$ vim src/pages/index/App.vue
<template>
    <router-view/>
</template>

<script>
    export default {
        name: "app",
        components:{},
        data(){
            return {
            };
        },
        created(){
            //console.log(this.$route);
        },
        methods:{
        }
    }
</script>

<style scoped>
</style>

Vue的组件系统提供了一种抽象,使用独立可复用的组件来构建大型应用。因此,几乎任意类型应用的界面都可以抽象为一个组件树。

单页组件由三部分组成

  • <template> 视图模板
  • <script> 组件定义
  • <style> 组件样式表
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容