巧用Webpack

Webpack是实现我们前端项目工程化的基础,但其实她的用处远不仅仅如此,我们可以通过Webpack来帮我们做一些自动化的事情。首先我们要了解require.context()这个API

require.context()

您可以使用require.context()函数创建自己的上下文。 它允许您传入一个目录进行搜索,一个标志指示是否应该搜索子目录,还有一个正则表达式来匹配文件。

其实是Webpack通过解析 require() 的调用,提取出来如下这些信息:

Directory: ./template

Regular expression: /^.*\.ejs$/

然后来创建我们自己的上下文,什么意思呢,就是我们可以通过这个方法筛选出来我们需要的文件并且读取

下面我们来简单看一看使用:

/**

* @param directory 要搜索的文件夹目录不能是变量,否则在编译阶段无法定位目录

* @param useSubdirectories 是否搜索子目录

* @param regExp 匹配文件的正则表达式

* @return function 返回一个具有 resolve, keys, id 三个属性的方法

resolve() 它返回请求被解析后得到的模块 id

keys() 它返回一个数组,由所有符合上下文模块处理的请求组成。

id 是上下文模块里面所包含的模块 id. 它可能在你使用 module.hot.accept 的时候被用到

*/

require.context('demo', useSubdirectories = false, regExp = /\.js$/)

// (创建了)一个包含了 demo 文件夹(不包含子目录)下面的、所有文件名以 `js` 结尾的、能被 require 请求到的文件的上下文。

不要困惑,接下来我们来探讨在项目中怎么用。

组织路由

对于Vue中的路由,大家都很熟悉,类似于声明式的配置文件,其实已经很简洁了。现在我们来让他更简洁。

1、分割路由

首先为了方便我们管理,我们把router目录下的文件分割为以下结构:

router // 路由文件夹

|__index.js // 路由组织器:用来初始化路由等等

|__common.js // 通用路由:声明通用路由

|__modules // 业务逻辑模块:所以的业务逻辑模块

|__index.js // 自动化处理文件:自动引入路由的核心文件

|__home.js // 业务模块home:业务模块

|__a.js // 业务模块a

2、modules文件夹中处理业务模块

modules文件夹中存放着我们所有的业务逻辑模块,至于业务逻辑模块怎么分,我相信大家自然有自己的一套标准。我们通过上面提到的require.context()接下来编写自动化的核心部分index.js。

const files = require.context('.', true, /\.js$/)

console.log(files.keys()) // ["./home.js"] 返回一个数组

let configRouters = []

/**

* inject routers

*/

files.keys().forEach(key => {

if (key === './index.js') return

configRouters = configRouters.concat(files(key).default) // 读取出文件中的default模块

})

export default configRouters // 抛出一个Vue-router期待的结构的数组

自动化部分写完了,那业务组件部分怎么写? 这就更简单了。

import Frame from '@/views/frame/Frame'

import Home from '@/views/index/index'

export default [

// 首页

{

path: '/index',

name: '首页',

redirect: '/index',

component: Frame,

children: [ // 嵌套路由

{

path: '',

component: Home

}

]

}

]

3、common路由处理 我们的项目中有一大堆的公共路由需要处理比如404,503等等路由我们都在common.js中进行处理。

export default [

// 默认页面

{

path: '/',

redirect: '/index',

hidden:true

},

// 无权限页面

{

path: '/nopermission',

name: 'nopermission',

component: () => import('@/views/NoPermission')

},

// 404

{

path: '*',

name: 'lost',

component: () => import('@/views/404')

}

]

4、路由初始化这是我们的最后一步了,用来初始化我们的项目路由。

import Vue from 'vue'

import VueRouter from 'vue-router'

import RouterConfig from './modules' // 引入业务逻辑模块

import CommonRouters from './common' // 引入通用模块

Vue.use(VueRouter)

export default new VueRouter({

mode: 'history',// 需要服务端支持

scrollBehavior: () => ({ y: 0 }),

routes: RouterConfig.concat(CommonRouters)

})

估计有些朋友代码写到这还不知道到底这样做好处在哪里。我们来描述一个场景,比如按照这种结构来划分模块。正常的情况是我们创建完home.js要手动的把这个模块import到路由文件声明的地方去使用。但是有了上面的index.js,在使用的时候你只需要去创建一个home.js并抛出一个符合VueRouter规范的数组,剩下的就不用管了。import RouterConfig from './modules' // 引入业务逻辑模块 已经帮你处理完了。另外扩展的话你还可以把hooks拿出来作为一个单独文件。

全局组件统一声明

同样的道理,有了上面的经验,我们照葫芦画瓢来处理一下我们的全局组件。这就没什么可说的了,直接上核心代码。

1、组织结构

components // 组件文件夹

|__xxx.vue // 其他组件

|__global // 全局组件文件夹

|__index.js // 自动化处理文件

|__demo.vue // 全局demo组件

2、global处理

import Vue from 'vue'

let contexts = require.context('.', false, /\.vue$/)

contexts.keys().forEach(component => {

let componentEntity = contexts(component).default

// 使用内置的组件名称 进行全局组件注册

Vue.component(componentEntity.name, componentEntity)

})

3、使用和说明

这个使用起来就更简单了,直接在app.js引用这个文件就行。

注意:我之前看到有些人做法是使用组件名去区分全局组件和普通组件,然后通过正则去判断需不需要全局注册。我是直接把全局的组件放到global文件夹下,然后组件的注册名称直接使用component.name。至于使用哪种方式就比较看个人了。

充分利用NodeJS

放着node这么好的东西不用真是有点浪费,那么我们来看看node能为我们提高效率做出什么贡献。

有这么一个场景,我们每次创建模块的时候都要新建一个vue文件和对应的router配置,而且新页面的大部分东西都还差不多,还得去复制粘贴别的页面。这想想就有点low。那既然有node我们可不可以通过node来做这些乱七八糟的事情? 下面来把我们的想法付诸于现实。

我们实现这个功能主要要借助Node的fs和process, 感兴趣的话可以深入研究一下。

首先我们要编写我们的node脚本,这里是一个比较简单的版本。什么验证文件夹或者文件的都没有,只是来实现我们这个想法:

/*

* fast add new module script

*/

const path = require('path')

const fs = require('fs')

const chalk = require('chalk')

const reslove = file => path.resolve(__dirname, '../src', file)

// symbol const

const RouterSymbol = Symbol('router'),

ViewsSymbol = Symbol('views')

// root path

const rootPath = {

[RouterSymbol]: reslove('router/modules'),

[ViewsSymbol]: reslove('views')

}

//loggs

const errorLog = error => console.log(chalk.red(`${error}`))

const defaultLog = log => console.log(chalk.green(`${log}`))

// module name

let moduleName = new String()

let fileType = new String()

//const string

const vueFile = module => (`<template>

</template>

<script>

export default {

name: '${module}',

data () {

return {

}

},

methods: {

},

created() {

}

}

</script>

<style lang="less">

</style>

`)

// route file

const routerFile = module => (`// write your comment here...

export default [

{

path: '/${module}',

name: '',

redirect: '/${module}',

component: () => import('@/views/frame/Frame'),

children: [

{

path: '',

fullPath: '',

name: '',

component: () => import('@/views/${module}/index')

}

]

}

]

`)

/**

* generate file

* @param {*} filePath

* @param {*} content

* @param {*} dirPath

*/

const generateFile = async (filePath, content, dirPath = '') =>{

try {

// create file if file not exit

if (dirPath !== '' && ! await fs.existsSync(dirPath)) {

await fs.mkdirSync(dirPath)

defaultLog(`created ${dirPath}`)

}

if (! await fs.existsSync(filePath)) {

// create file

await fs.openSync(filePath, 'w')

defaultLog(`created ${filePath}`)

}

await fs.writeFileSync(filePath, content, 'utf8')

} catch (error) {

errorLog(error)

}

}

// module-method map

const generates = new Map([

['view', async (module) => {

// module file

const filePath = path.join(rootPath[ViewsSymbol], module)

const vuePath = path.join(filePath, '/index.vue')

await generateFile(vuePath, vueFile(module), filePath)

}],

// router is not need new folder

['router',async (module) => {

const routerPath = path.join(rootPath[RouterSymbol], `/${module}.js`)

await generateFile(routerPath, routerFile(module))

}]

])

defaultLog(`请输入模块名称(英文):`)

// files

const files = ['view', 'router']

// 和命令行进行交互 获取的创建的模块名称

process.stdin.on('data', (chunk) => {

try {

if (!moduleName) {

moduleName = chunk

} else {

chunk = chunk.slice(0,-2) // delete /n

defaultLog(`new module name is ${chunk}`)

files.forEach(async (el, index) => {

// 执行创建语句

await generates.get(`${el}`).call(null, chunk.toString())

if (index === files.length-1) {

process.stdin.emit('end')

}

})

}

} catch (error) {

errorLog(error)

}

})

process.stdin.on('end', () => {

defaultLog('create module success')

})

下面我们看使用的流程

这样我们就分别创建了vue和router的文件,而且已经注入了内容。按照我们提前声明的组件

注意:这只是一个简单的思路,通过Node强大的文件处理能力,我们能做的事情远不止这些。

发挥Mixins的威力

Vue中的混入mixins是一种提供分发 Vue 组件中可复用功能的非常灵活的方式。听说在3.0版本中可能会用Hooks的形式实现,但这并不妨碍它的强大。这里主要来讨论mixins能在什么情景下帮助我们。

通用mixins

如果我们有大量的表格页面,仔细一扒拉你发现非常多的东西都是可以复用的例如分页,表格高度,加载方法, laoding声明等一大堆的东西。下面我们来整理出来一个简单通用混入list.js

const list = {

data () {

return {

// 这些东西我们在list中处理,就不需要在每个页面再去手动的做这个了。

loading: false, // 伴随loading状态

pageNo: 1, // 页码

pageSize: 15, // 页长

totalCount: 0, // 总个数

pageSizes: [15, 20, 25, 30], //页长数

pageLayout: 'total, sizes, prev, pager, next, jumper', // 分页布局

list: []

}

},

methods: {

// 分页回调事件

handleSizeChange(val) {

this.pageSize = val

// todo

},

handleCurrentChange (val) {

this.pageNo = val

// todo

},

/**

* 表格数据请求成功的回调 处理完公共的部分(分页,loading取消)之后把控制权交给页面

* @param {*} apiResult

* @returns {*} promise

*/

listSuccessCb (apiResult = {}) {

return new Promise((reslove, reject) => {

let tempList = [] // 临时list

try {

this.loading = false

// todo

// 直接抛出

reslove(tempList)

} catch (error) {

reject(error)

}

})

},

/**

* 处理异常情况

* ==> 简单处理 仅仅是对表格处理为空以及取消loading

*/

listExceptionCb (error) {

this.loading = false

console.error(error)

}

},

created() {

// 这个生命周期是在使用组件的生命周期之前

this.$nextTick().then(() => {

// todo

})

}

}

export default list

下面我们直接在组件中使用这个mixins

import mixin from '@/mixins/list' // 引入

import {getList} from '@/api/demo'

export default {

name: 'mixins-demo',

mixins: [mixin], // 使用mixins

data () {

return {

}

},

methods: {

// 加载列表

load () {

const para = {

}

this.loading = true

getList(para).then((result) => {

this.listSuccessCb(result).then((list) => {

this.list = list

}).catch((err) => {

console.log(err)

})

}).catch((err) => {

this.listExceptionCb(err)

})

}

},

created() {

this.load()

}

}

使用了mixins之后一个简单的有loadoing, 分页,数据的表格大概就只需要上面这些代码。

mixins做公共数据的管理

有些时候我们有一些公共的数据它可能3,4个模块取使用但是又达不到全局的这种规模。这个时候我们就可以用mixins去管理他们,比如我们有几个模块要使用用户类型这个列表,我们来看使用mixins来实现共享。

// types.js

import {getTypes} from '@/api/demo' // ajax

export default {

data () {

return {

types: [] // ==> {name: '', value: ''}

}

},

methods: {

// 获取列表

getAllTypesList () {

getApiList().then((result) => {

// todo

this.types = result // 假设result就是我们需要使用的数据

}).catch((err) => {

console.error(err)

})

}

},

created() {

// 在需要使用这个mixins的时候取自动请求数据 这个可要可不要 你想在父组件中执行也是ok的

this.getAllTypesList()

}

}

在组件中引用

import typeMixin from '@/mixins/types'

export default {

name: 'template',

mixins: [typeMixin],

data () {

return {

// types这个数组在使用组件中不用多余的定义,直接拿来用就行

type: ''

}

},

methods: {

}

}

至于mixins中得数据我们可以在组件中直接使用

<!-- -->

<el-select v-model="type" clearable placeholder="请选择类型">

<el-option v-for="item in types" :key="item.id" :label="item.templateName" :value="item.id"></el-option>

</el-select>

我们这样就可以不用vuex来去管理那些只有在模块间复用几次的数据,而且非常方便得去取我们想要得数据,连定义都省了。但是这有一个缺点。就是每次都会去重新请求这些数据。如果你不在乎这一点点瑕疵的话,我觉得用起来是完全ok的。

注意: mixins它固然是简单的,但是注释和引用一定要做好,不然的话新成员进入团队大概是一脸的懵逼,而且也不利于后期的维护。也是一把双刃剑。另外:全局mixins一定要慎用,如果不是必须要用的话我还是不建议使用。

进一步封装组件

大家都知道组件化的最大的好处就是高度的可复用性和灵活性。但是组件怎么封装好,封装到什么程度让我们更方便。这是没有标准的答案的。我们只有根据高内聚,低耦合的这个指导思想来对我们的业务通用组件来进行封装,让我们的业务页面结构更加的简洁,加快我们的开发效率。封装多一点的话页面可能会变成这样:

<template>

<box-content>

<!-- 头部标题部分 -->

<page-title>

<bread slot="title" :crumbs="[{name: 'xx管理', path: '', active: true, icon: ''}, {name: 'xxxx', path: '', active: true, icon: ''}]"></bread>

</page-title>

<!-- 表格部分 -->

<div>

<base-table v-loading="loading" :columns="headers" :list="list" :page-no ="pageNo" :page-size="pageSize" :total-count="totalCount" @delete="deleteItm" @change-size="handleSizeChange" @change-page="handleCurrentChange">

</base-table>

</div>

</box-content>

</template>

有什么东西一目了然。

无状态组件

最容易勾起我们封装欲望的就是无状态HTML组件,例如我们除去header, menu之后的content部分。没有什么需要复杂的交互,但是我们每个页面又都得写。你说不拿它开刀拿谁开

加快Vue项目的开发速度的更多相关文章

  1. 如何加快Vue项目的开发速度

    如何加快Vue项目的开发速度 本文摘自奇舞周刊,侵权删. 现如今的开发,比如内部使用的管理平台这种项目大都时间比较仓促.实际上来说,在使用了webpack + vue 这一套来开发的话已经大大了提高了 ...

  2. vue项目的开发

    vue项目的开发 我们已经通过命令行创建了一个vue项目,并且打开了这个项目.下面是这个文件的src文件夹,这个文件夹放了整个项目的核心代码. 一.vue文件的用处简介. 1.assets文件夹,用来 ...

  3. Intellij idea 12和设置快捷键修改(加快项目的开发速度与养成良好习惯)

    1.为了养成良好的代码习惯idead中的javascript jSLint能显示不良的代码设置如下    2.Intellij idea 12每一次修改,保存生成都要按ctrl+shift+F9非常影 ...

  4. Vue项目在开发环境跨域和生成环境部署跨域问题解决

    一.在dev环境下的跨域问题解决1.项目使用的是axios请求网络,将baseUrl修改为/api (这里是使用webpack提供的代理功能将/api代理成目标接口host) axios.defaul ...

  5. vue 项目的开发流程

    1.$ node -v (检测node版本,node版本需要在 V4 以上) 2.全局安装vue $ npm install -g vue 3.安装脚手架 $ npm install -g vue-c ...

  6. 使用 typescript ,提升 vue 项目的开发体验(1)

    此文已由作者张汉锐授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 前言:对于我们而言,typescript 更像一个工具 官方指南 从 vue2.5 之后,vue 对 ts ...

  7. 使用 typescript ,提升 vue 项目的开发体验(2)

    此文已由作者张汉锐授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. vuex-class 提供了和 vuex 相关的全部装饰器,从而解决了上面 Vue.extend + vue ...

  8. vue项目 首页开发 part2

    知识点回顾 git clone: 从远程服务器克隆一个一模一样的版本库到本地,复制的是整个版本库, 叫做clone.(clone是将一个库复制到你的本地,是一个本地从无到有的过程) 1. 创建分支 进 ...

  9. Vue项目的开发流程

    我先安装的node.js 1.确认已安装了node.js,可在cmd中输入( node -v和npm -v),如显示出版号,说明安装成功 2.安装webpack 和webpack-cli 在全局下安装 ...

随机推荐

  1. [NOIP2003] 传染病控制 搜索+剪枝

    搜索的最广泛应用优化——剪枝 这道题的dp和贪心都是无正确性的,所以,搜~~~~~~~ 搜的时候你发现不剪枝极容易被卡掉(然而良心NOIP没有这么做,不剪枝仍然飞快),所以我们需要一些玄学的剪枝最常见 ...

  2. 面试前需要弄懂的SQL

    说明:创建数据库 view source   print? 1 Create DATABASE database-name 说明:删除数据库 view source   print? 1 drop d ...

  3. Linux产生背景

    By francis_hao Oct 26,2016 很久很久以前,大概在1965年左右,由贝尔实验室(Bell).麻省理工学院(MIT)及通用电气公司(GE)共同发起了一个叫做Multics的项目, ...

  4. spring事务不回滚 自己抛的异常

    在service代码中   throw new Excepion("自定义异常“) 发现没有回滚, 然后百度了下, 改为抛出运行时异常  throw new RuntimeException ...

  5. Ubuntu下安装LNMP之nginx的安装

    Nginx 最初是作为一个 Web 服务器创建的,用于解决 C10k 的问题.作为一个 Web 服务器,它可以以惊人的速度为您的数据服务.但 Nginx 不仅仅是一个 Web 服务器,你还可以将其用作 ...

  6. 近期对于windows服务的理解

    1.APP.config的作用   在开发环境下时,根目录下的APP.config里面会填写一些参数之类的.当生成之后,这些参数将会被自动生成在*.exe文件目录中.如图: 其中,.exe文件为Win ...

  7. Codeforces Round #348 (VK Cup 2016 Round 2, Div. 2 Edition) A

    A. Little Artem and Presents time limit per test 2 seconds memory limit per test 256 megabytes input ...

  8. Firefox多国语言多OS离线安装包

    Download Firefox in your language Firefox is made in large part by volunteers around the world. That ...

  9. 在Debian9安装node和npm

    这学期又快结束了,坐在每天面对的电脑面,本着整理资料.更换心情的目的,我重装了一下自己的debian.下面就将自己安装node的过程进行记录与分享. node的官网:https://nodejs.or ...

  10. 请把<ul><li>第1行</li><li>第2行</li>...</ul>(ul之间有10个li元素)插入body里面,注意:需要考虑到性能问题。

    var html = [],i;for(i = 0; i < 10; i++){ html.push('<ul><li>第' + (i+1) + '行</li> ...