vue-cli+webpack项目结构,有所修改

1,546 阅读14分钟

新建项目

针对本公司后端开发人员

  • 拉取项目后打开
  • npm install
  • npm run dev

使用技术

  • vue+vue-router+vue-cli

  • 使用eslintstylelint进行代码规范

  • 样式编写使用css或者less

  • 可以安装jsDoc编写jsAPI文档生成器

  • webpack静态模块打包(通常vue-cli都是使用webpack模板包)

其实唯一改变的就是之前的.html文件变成了.vue文件,相当于写了一个一个的组件;然后新增了vue-router和webpack模块化工具

项目结构以及规范

目的:方便大家快速定位文件,以及明白新增文件存放位置

  • bulidwebpack相关配置以及脚本文件, 在实际开发过程中主要会用到webpack.base.conf.js,配置less、babel等编译器

  • config:服务器相关配置,常用到config/index.js 配置开发环境的 端口号、是否开启热加载 或者 设置生产环境的静态资源相对路径、是否开启gzip压缩、npm run build命令打包生成静态资源的名称和路径等。

  • doc:相关文档,可以认真查阅一遍

  • node_modules:存放install的时候的各种依赖包

  • src:前端源码存放地方,包括项目的源码以及资源,包括页面图片、路由组件、路由配置、入口文件等

    • components:组件存放地方;

      • app存放业务组件

      • common存放通用组件

      • config组件的配置文件

      • template模板页面(可以直接拷贝进行修改变成自己的组件)

      • tools工具类

        // 函数防抖
        import debounce from '@/components/tools/extend/debounce.js';
        // 函数节流
        import throttle from '@/components/tools/extend/throttle.js';
        // 获取兼容ie和非ie的event对象
        import getEvent from '@/components/tools/extend/getEvent.js';
        // 阻止默认事件
        import stopEvent from '@/components/tools/extend/stopEvent.js';
        // 绑定|取消事件函数
        import event from '@/components/tools/extend/event.js';
        // 执行时间记录工具
        import fdPerformance from '@/components/tools/extend/performance.js';
        // 日志工具
        import fdLog from '@/components/tools/extend/log.js';
        // console日志模块, 因为console为全局对象,所以用$console为变量名
        import $console from '@/components/tools/extend/console.js';
        // 判断浏览器的对象
        import browser from '@/components/tools/extend/browser.js';
        // 处理了的ajax
        import ajax from '@/components/tools/extend/ajax.js';
        // 未处理的axios
        import axios from 'axios';
        

        一个一个的页面我们称为模块,模块是很多组件组成,组件由元件组成

        工具类里面是一堆写好的公用方法如上图所示已经全部在global中引入,global已经在main里面全局引入,所以可以直接使用

        业务组件的css全部写到外层css目录components目录下

        通用组件的css直接在组件内部建一个index.less,单独编写,方便其他系统复用

    • css:样式存放

      • animation: 动画样式 (非必须),可以编写公共动画然后引入

        common:公用类;normalize.less和base.less是重置浏览器的样式,function.less 这里全部是功能性命名

        components: 组件类css, 内部目录结构和组件的文件夹一致

        config: 配置类css,因为采用less语法,所以可以配置变量 (非必须)

        mixins: 混入类css,加强css的复用 (非必须),编写通用的方法,可在组件之间通用

        pages: 页面模块类css, 各个页面的css

        unit: 元件类css(非必须)

        index.less是整个项目的css入口,里面串联各类型的css

    • jsjs脚本存放

      • app:项目依赖的、所有自己写的js放这里

        config.js 项目的配置文件,包含里请求方法,后端服务器地址,日志,调试多个配置项,大家可以在这里配置项目需要的其他项

        server-config.js 此文件为后端接口api的配置文件

        少了lib外部引入库,现在外部引入可以直接install安装依赖包,查看现在有哪些外部包可以在package.json里面查询

        config.jsserver-config.js是之前的config.js的拆分,只是把服务端的请求接口拿出来了

    • pages:路由用的各个页面, 按照模块划分内部结构,里面存在index.vue 作为页面入口

    • images:图片

    • router:路由配置

  • static:静态资源,主要放不会更改的文件,比如mock json,需要依赖的外部js、静态图片资源等

    vue-cli有两个放置静态资源的地方,分别是src/assets文件夹和static文件夹 , assets目录中的文件会被webpack处理解析为模块依赖,只支持相对路径形式

    static/ 目录下的文件并不会被Webpack处理:它们会直接被复制到最终的打包目录(默认是dist/static)下。必须使用绝对路径引用这些文件,这是通过在config.js文件中的build.assetsPublicPathbuild.assetsSubDirectory 连接来确定的 任何放在 static/ 中文件需要以绝对路径的形式引用:/static/[filename]

    在这个项目结构中没有assets文件所以不用考虑

  • test:单元测试

  • .eslintrc:eslint 配置 eslintrcDoc.jseslint每项说明文档

  • .stylelintrcstylelint配置stylelintrcDoc.jsstylintlint每项说明文档

  • .babelrc : babel配置

  • .jsdoc.conf.jsonjsdoc文档生成配置文件

  • .gitignore: git 忽略文件配置

  • .eslintignoreeslint验证忽略配置

  • package.json:项目包管理文件

  • index.html:页面入口,经过编译之后的代码将插入到这来

vue-router

单页应用的路径管理器, vue 的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来 ,路由之间的切换就是组件之间的切换;和传统页面的超链接很像

  • 实现原理:更新某个指定容器中的内容,而不是重新请求页面

  • 两种方式: Hash模式(默认模式)和History模式

    • hash模式:改变#不触发网页重载,常用于锚点定位,会滚动到相应位置,每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置 ,hash 模式的原理是onhashchange事件(监测hash值变化),可以在 window 对象上监听这个事件

    • History模式: hash模式会在url中自带# ,想去掉#可以使用history模式

      //router文件中
      export default new Router({
        mode: 'history',
        routes: [{
            path: "/", 
            redirect: "/home"
        },{
            path: "/home", 
            name: "home", 
            component:Home
        }]
      })
          
      //利用html5 history interface 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器记录栈,提供了对浏览器历史记录修改的功能。只是当它们执行修改时,虽然改变了当前的URL,但浏览器不会立即向后端发送请求。
      //注意:因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404;在服务端增加一个覆盖所有情况的候选资源,如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
      
    • 路由的使用

      npm install vue-router -s //下载
      import VueRouter from 'vue-router';//在main.js中引入
      Vue.use(VueRouter);//安装插件挂载属性
      let router = new VueRouter({routes:[{path:'/home',component:Home}]});//创建路由对象并配置路由规则
      router:router//挂载路由到new的vue实例中
      <router-view/>//在app.vue中找个地方放组件
      
    • 路由的跳转

      • 直接修改地址栏
      • this.$router.push(‘路由地址’)
      • <router-link to="路由地址"></router-link>
    • 路由传参和接收参数

      • 使用name传参,接收$route.name

      • 通过router-link 标签中的to传参

        <router-link :to="{name:'home',params:{username:'jspang',id:'555'}}">Hi页面1</router-link>
        this.$router.push({ name: 'home', params:{username:'jspang',id:'555'}})
        
        {{$route.params.username}}-{{$route.params.id}}
        
      • url传参

        // 路由配置
        {
            path:'/home/:newsId/:newsTitle',
            component:home
        }
        
        // 传参
        <router-link to="/home/198/jspang website is very good">params</router-link>
        
        // 接收
        {{ $route.params.newsId}}
        {{ $route.params.newsTitle}}
        
      • query传参

        <router-link :to="{ path: '/home',query: { queryId:  status }}" >
             router-link跳转Query
        </router-link>
        this.$router.push({ path: '/home', query: { queryId: status }});
        
        
        // 接收
        this.$route.query.queryId
        
        

        命名路由传递参数需要使用params来传递, 命名路由这种方式传递的参数,如果在目标页面刷新是会出错的

        传递参数使用query而且必须配合path来传递参数而不能用name

    • $route和$router的区别

      //$route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。
      $route.path
      //字符串,对应当前路由的路径,总是解析为绝对路径,如 "/home"。
      $route.params
      //一个 key/value 对象,如果没有路由参数,就是一个空对象。
      $route.query
      //一个 key/value 对象,表示URL查询参数。对于路径/home?user=1,$route.query.user为1,如果没有查询参数,则是个空对象。
      $route.hash
      //当前路由的hash值(不带#),如果没有hash值,则为空字符串。
      $route.fullPath
      //完成解析后的 URL,包含查询参数和 hash 的完整路径
      $route.matched
      //数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。
      $route.name //当前路径名字
      
      

      $router 是“路由实例”对象,即使用new VueRouter创建的实例,包括了路由的跳转方法,钩子函数等

      this.$router.go(-1)//跳转到上一次浏览的页面
      this.$router.replace('/home')//指定跳转的地址
      this.$router.replace({name:'home'})//指定跳转路由的名字下
      this.$router.push('/home')//通过push进行跳转
      this.$router.push({name:'home'})//通过push进行跳转路由的名字下
      
      

      使用push方法的跳转会向 history 栈添加一个新的记录,当我们点击浏览器的返回按钮时可以看到之前的页面。

      使用replace方法不会向 history 添加新记录,而是替换掉当前的 history 记录,即当replace跳转到的网页后,‘后退’按钮不能查看之前的页面。

webpack

vue-cli是构建vue单页应用的脚手架,输入一串指定的命令行从而自动生成vue.js+webpack的项目模板 ,webpack发挥了很大的作用,它使得我们的代码模块化,引入一些插件帮我们完善功能可以将文件打包压缩,图片转base64等;打包之后生成dist文件,这里面只有静态资源和以和一个index.html页面

  • package.json

    • package.json来制定名单,需要哪些npm包来参与到项目中来,npm install命令根据这个配置文件增减来管理本地的安装包 ,比如我们安装了jquery,会在这个配置文件中体现

      {
        //从name到private都是在脚手架搭建中输入的项目描述
        "name": "default",//项目名称
        "version": "1.0.0",//项目版本号
        "description": "fd  default  project ",//项目描述
        "author": "yangf",//作者
        "private": true,//是否私有,如果私有不会被发布到npm的线上仓库中去
         //scripts中的子项即是我们在控制台运行的脚本的缩写npm run key值 = npm run value值
        "scripts": {
          //webpack-dev-server:启动了http服务器,实现实时编译;
          //inline模式会在webpack.config.js入口配置中新增webpack-dev-server/client?http:localhost:8080/的入口,使得我们访问路径为localhost:8080/index.html
          "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
          "start": "npm run dev",
          "unit": "jest --config test/unit/jest.conf.js --coverage",
          "doc": "jsdoc -r -c .jsdoc.conf.json",
          "test": "npm run unit",
          "lint": "eslint --fix --ext .js,.vue src test/unit  config  build",
          //使用node运行build文件
          "build": "node build/build.js"
        },
        //(项目依赖库):在安装时使用--save则写入到dependencies
        "dependencies": {
          "axios": "^0.19.0",
          "babel-polyfill": "^6.26.0",
          "vue": "^2.6.0",
          "vue-router": "^3.0.1"
        },
        //(开发依赖库):在安装时使用--save-dev将写入到devDependencies
        "devDependencies": {
          "autoprefixer": "^7.1.2",
          "babel-core": "^6.22.1"
        },
        //指定node和npm版本
        "engines": {
          "node": ">= 6.0.0",
          "npm": ">= 3.0.0"
        },
        //限制了浏览器或者客户端需要什么版本才可运行
        "browserslist": [
          "> 1%",
          "last 2 versions",
          "not ie <= 8"
        ]
      }
      
      

      devDependencies里面的插件只用于开发环境,不用于生产环境,即辅助作用,打包的时候需要,打包完成就不需要了。而dependencies是需要发布到生产环境的,自始至终都在。比如webpack等只是在开发中使用的包就写入到devDependencies,而像vue这种项目全程依赖的包要写入到dependencies

  • .postcssrc.js

    • 为兼容所有浏览器,有的css属性需要对不同的浏览器加上前缀
    module.exports = {
        "plugins": {
        "postcss-import": {},// 用于@import导入css文件
        "postcss-url": {},// 路径引入css文件或node_modules文件
        //编辑目标浏览器:使用package.json中的“browserslist”字段
        "autoprefixer": {}
      }
    }
    
    
  • .babelrces6 解析的配置

    {
      //制定转码规则
      "presets": [
        //env是使用babel-preset-env插件将js进行转码成es5,设置设置amd,commonjs这样的模块化文件,不进行转码
        ["env", {
          "modules": false,
          "targets": {
            "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
          }
        }],
        "stage-2"
      ],
      //使用外部插件来进行转码
      "plugins": ["transform-vue-jsx", "transform-runtime"],
      "env": {
        "test": {//提前配置的环境变量
          "presets": ["env", "stage-2"],
          "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
        }
      }
    }
    
    
  • config文件

    config内的文件其实是服务于build的,大部分是定义一个变量export出去,通过修改配置文件,让启动和打包项目时根据不同的命令,达到预期的结果

    • dev.env.js
    'use strict'
    //为Webpack设计的合并,提供了一个合并函数,它将数组和合并对象创建一个新对象。
    const merge = require('webpack-merge')
    const prodEnv = require('./prod.env')
    module.exports = merge(prodEnv, {
      NODE_ENV: '"development"'
    })
    
    //https://www.npmjs.com/package/webpack-merge
    
    
    • prod.env.js

      当开发时调取dev.env.js的开发环境配置,发布时调用prod.env.js的生产环境配置

    • index.js

var  path = require('path')

module.exports = {
  dev: {//开发环境下的配置
    assetsSubDirectory: 'static',//子目录
    assetsPublicPath: '/',//根目录
    proxyTable: {},//可利用该属性解决跨域的问题
    host: '127.0.0.1', // 地址
    port: 8080, //端口号设置,端口号占用出现问题可在此处修改
    autoOpenBrowser: false,//是否在编译后设置自动打开页面
    errorOverlay: true,//浏览器错误提示
    notifyOnErrors: true,//跨平台错误提示
    poll: false, //使用文件系统(file system)获取文件改动的通知devServer.watchOptions
    useEslint: true,//使用eslint
    showEslintErrorsInOverlay: false,//是否使用eslint全屏报错提示
    devtool: 'cheap-module-eval-source-map',//增加调试,该属性为原始源代码(仅限行)不可在生产环境中使用
    cacheBusting: true,//使缓存失效
    cssSourceMap: true
  },
  build: {// 生成环境下的配置
    index: path.resolve(__dirname, '../dist/index.html'),//index编译后生成的位置和名字
    assetsRoot: path.resolve(__dirname, '../dist'),//编译后存放生成环境代码的位置
    assetsSubDirectory: 'static',//js,css,images存放文件夹名
    assetsPublicPath: './',//发布的根目录,通常为./如果是上线的文件,可根据文件存放位置进行更改路径
    productionSourceMap: true,
    devtool: '#source-map',// 是否使用 #source-map 开发工具
    productionGzip: false,// 是否开启 gzip
    productionGzipExtensions: ['js', 'css'],// 需要使用 gzip 压缩的文件扩展名
    bundleAnalyzerReport: process.env.npm_config_report//是否使用可视化的分析工具
  }
}
//代码压缩后bug定位很困难,引入sourcemap记录压缩前后的位置信息记录,当产生错误时直接定位到未压缩前的位置

  • build

    • build.js

      构建生产版本,package.json中的scripts的build就是node build/build.js,输入命令行npm run build对该文件进行编译生成生产环境的代码

'use strict'
require('./check-versions')()//调用检查版本的文件
process.env.NODE_ENV = 'production'//设置当前是生产环境
//下面定义常量引入插件
const ora = require('ora')
const rm = require('rimraf')//删除文件
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')//默认读取下面的index.js文件
const webpackConfig = require('./webpack.prod.conf')

const spinner = ora('building for production...')
spinner.start()//调用start的方法实现加载动画,优化用户体验
//先删除dist文件再生成新文件,因为有时候会使用hash来命名,删除整个文件可避免冗余
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, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
      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'
    ))
  })
})

  • check-version.js 该文件用于检测node和npm的版本,实现版本依赖

  • utils.js 一个用来处理css文件的工具

  • loader.conf.js 处理.vue文件,解析这个文件中的每个语言块(template、script、style),转换成js可用的js模块。

  • webpack.base.conf.js是开发和生产共同使用提出来的基础配置文件,主要实现配制入口,配置输出环境,配置模块resolve和插件等

  • development模式下,将侧重于功能调试和优化开发体验,包含如下内容:

    • 浏览器调试工具
    • 开发阶段的详细错误日志和提示
    • 快速和优化的增量构建机制
  • production模式下,将侧重于模块体积优化和线上部署,包含如下内容:

    • 开启所有的优化代码
    • 更小的bundle大小
    • 去除掉只在开发阶段运行的代码
    • Scope hoisting和Tree-shaking
    • 自动启用uglifyjs对代码进行压缩

exports/module.exports/export default

  • module.exports 对象是由模块系统创建的

    // 返回一个对象
    var app = {
        name: 'app',
        version: '1.0.0',
        sayName: function(name){
            console.log(this.name);
        }
    }
    module.exports = app;
    // 调用
    var app = require('./app.js');
    app.sayName('hello');
    
    // 返回一个构造函数
    var CLASS = function(args){
         this.args = args;
    }
    module.exports = CLASS;
    // 调用
    var CLASS = require('./CLASS.js');
    varc = new CLASS('arguments');
    
    //返回一个实例对象
    var CLASS = function(){
        this.name = "class";
    }
    CLASS .prototype.func = function(){
        alert(this.name);
    }
    module.exports = new CLASS();
    //调用
    var c = require('./CLASS.js');
    c.func();//"class"
    
    

    require导出的内容是module.exports的指向的内存块内容,并不是exports的。区别就是 exports 只是module.exports的引用,辅助后者添加内容用的。用内存指向的方式更好理解。

    module.exports指向新的对象时,exports 断开了与module.exports的引用,那么通过exports = module.exports让 exports 重新指向module.exports 即可。

  • export default 在一个模块里只能有一个,但是export可以有多个

    //model.js
    let e1='export 1';
    let e2='export 2';
    let e3='export 3';
    let e4='export 4';
    export {e2};
    export {e3};
    export {e4};
    export default e1;
    //使用模块的index.js
    import e1, {e2, e3, e4} from "./model";
    console.log(e1);
    console.log(e2);
    console.log(e3);
    console.log(e4);
    
    
  • 模块中通过export 导出的(属性或者方法)可以修改,但是通过export default导出的不可以修改

    //model.js
    let e1='export 1';
    let e2='export 2';
    export {e2};
    export default e1;
    e1='export 1 modified';
    e2='export 2 modified';
    //index.js
    import e1, {e2}from "./model";
    console.log(e1);//export 1
    console.log(e2);//export 2 modified
    
    

Node.js认为每个文件都是一个独立的模块。如果你的包有两个文件,假设是“a.js”“b.js”,然后“b.js”要使用“a.js”的功能,“a.js”必须要通过给 exports 对象增加属性来暴露这些功能: