项目踩坑系列(一)——vue-cli
文章内容大概包括:
- 选脚手架——vue-cli + 多页面配置
- 路由去中心化管理
- 动态标题
- 打包遇到的问题
- 一些比较细的小坑
因为搭建新项目需要,自己搞了一下webpack的东西。之前使用大多是别人搭好的架构,自己新建项目拿来用就好了,没有去自己动手搭过。现在就纯记录一下:
选脚手架 vue-cli
需求:一个完整的网站,包括首页、列表页、详情页、播放页等等约30个左右的页面。
同事推荐用cooking或者vue-cli搭建架构,cooking之前已经有在几个项目中实践过,据描述并不是很好用,配置有些没暴露,有些loader不好配。大概搞了一下之后决定用vue-cli。
先用npm全局安装vue-cli,还有vue-init,然后执行init来装webpack+vue,初始化项目的时候在命令行有一些选项要配置一下,初始化完成之后进入对应的项目,装一下各种依赖包。装完之后就可以直接npm run dev
跑一下啦,美滋滋。详细的步骤可以参考网上的文章,搜一下很多这方面的内容。
npm install -g @vue/cli
npm install -g @vue/cli-init
vue init webpack my-project
cd my-project
npm install
vue适合做单页应用,但是领导要求页面要做多页(原公司的项目有多页的,但是代码在原电脑里,所以时下也不方便去找),就网上找了下vue多页的配置。翻了一下之后找了一篇参考:使用Vue-cli搭建多页面应用时对项目结构和配置的调整来实际操作,按照文章来实际操作,没什么问题。但是每个页面都放了一个入口的html文件感觉比较麻烦,于是稍微做了一点调整。在上面文章的基础上修改了build/utils.js文件,把所有页面的入口文件都改成放在外面,不需要每个页面都放置一个入口模板(具体代码看后面)。
大概调整后的目录结构如下:
项目整体目录情况 多页,pages目录如下几个坑
配置过程中遇到的主要问题是:
1、在各种依赖包都安装完了的情况下,如果修改项目名,在跑npm run dev
的时候会直接报错。用万能的搜索引擎找了一下原因:npm项目,在安装依赖(node_nodules)时,会记录当前的文件路径,当修改之后就无法正常启动。也是比较蛋疼的,需要删除node_modules
目录,再重新npm install
。解决方案如下:
- 删除 node_modules 文件夹(如果修改项目名称,需要在在package.json中修改对应的name)
- 重新安装依赖 npm install
- 启动项目 npm run dev
2、在跑npm install
的时候,虽然自己已经设置了科学上网,但是还是会有一些依赖包装不了,所以还是借助淘宝npm镜像cnpm来安装。一行命令就搞定,然后cnpm install
美滋滋。虽然网上有人吐槽cnpm有时候会出现漏包的情况,不过我这边跑起来暂时没遇到这种情况。
npm install -g cnpm --registry=https://registry.npm.taobao.org
3、build打包的问题。在npm run build
的时候,因为一开始我的pages目录是这样子的:
虽然是在pages里面创建了不同命名的文件夹index live user
,但是入口html和js文件都是使用index
命名的,导致在build的时候生成的dist
目录下,只有一个index.html和index.js。然后我就方了,不是说多页面吗,怎么只有一个入口html和js。
遂重新去看了使用Vue-cli搭建多页面应用时对项目结构和配置的调整 文章下修改的具体配置,找到了build/utils.js
文件下的配置,发现了它不是取pages
目录下文件夹的名字作为打包完之后入口html和js文件的命名,而是直接取了文件夹内对应文件的名字,并将生成的文件都放在一个目录下,所以就直接覆盖了。
我这里就很尴尬的三个目录下的文件名都是index
。于是对uilts.js
文件进行修改,将html和js都修改成:直接提取pages
文件夹下的子文件夹的名字,作为 build完之后生成的html和js文件的命名。避免了pages不同目录下的文件名不能重复的情况,同时因为个人习惯,把不同目录下的入口js文件都命名为index.js
,方便查看。
调整后的pages
目录如下(这里我删去了之前的index.html,在第三步中说明):
3、pages
目录下的每个子目录,都有index.htm
l一个模板入口文件,内容都是一样的,每个目录下放一个相同的文件感觉不太喜欢,遂做一点修改,将其提取出来放在src
目录下,所有页面都用同一个模板来生成。
结合2和3的修改,utils.js
某个模块调整如下:
// yanzi 这块是对js的调整
//多入口配置
// 通过glob模块读取pages文件夹下的所有对应文件夹下的js后缀文件,如果该文件存在
// 那么就作为入口处理
exports.entries = function() {
var entryFiles = glob.sync(PAGE_PATH + '/*') //从原来的提取js路径作为filename改为提取目录路径
var map = {}
entryFiles.forEach((filePath) => {
let filename = filePath.split('/').pop() //从目录路径中提取目录名,作为build完js文件的命名
map[filename] = filePath + '/index.js' //不同页面的入口js统一命名为index.js
})
return map
}
// yanzi 这块是对html的调整
//多页面输出配置
// 与上面的多页面入口配置相同,读取pages文件夹下的对应的html后缀文件,然后放入数组中
exports.htmlPlugin = function() {
let entryHtml = glob.sync(PAGE_PATH + '/*') //从原来的提取html路径作为filename改为提取目录路径
let arr = []
entryHtml.forEach((filePath) => {
let filename = filePath.split('/').pop(); //从目录路径中提取目录名,作为build完html文件的命名
let conf = {
// 模板来源
// template: filePath,
template: path.resolve(__dirname, '../index.html'), //将模板来源改为src目录下的index.html
// 文件名称
filename: filename + '.html',
// 页面模板需要加对应的js脚本,如果不加这行则每个页面都会引入所有的js脚本
chunks: ['manifest', 'vendor', filename],
inject: true
}
if (process.env.NODE_ENV === 'production') {
conf = merge(conf, {
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: 'dependency'
})
}
arr.push(new HtmlWebpackPlugin(conf))
})
return arr
}
路由的去中心化管理
因为页面比较多,搭完项目之后开始搞路由。
user页面结构user是一个页面入口,下面有主页面App.vue
以及subPages
目录下的多个页面。而subPages
目录下的页面,如myVideos
文件夹中还有另外的subPages
,大概两层路由的情况。
在编写页面路由的时候,普通的做法是把所有vue文件都在外层的router.js
中引入,包括子页面的子页面。这样引入的工作量比较大(引入路径也长),而且集中在外层管理不太方便,生成的路由也会非常大难以管理。
所以,做了一个去中心化的管理。每个subPages
下的页面,都配有自己的routes.js
路由,然后在外层的router.js
再集中引入。用到的webpack属性是require.context
首先,在subPages
各个目录下先编写好自己子目录的路由,如myVIdeos/routes.js
的代码如下:
const myVideos = () => import('./myVideos.vue')
const myRecord = () => import('./subPages/myRecord.vue')
const videoList = () => import('./subPages/videoList.vue')
module.exports = [
{
path: '/myVideos',
component: myVideos,
name: 'myVideos',
children: [
{
path: 'myRecord',
name: 'myRecord',
component: myRecord
},{
path: 'videoList',
component: videoList,
name: 'videoList'
}
]
}
]
然后再在外层的router.js文件中,遍历subPages
目录下的各个routes.js
文件,动态加载,集中输出。router.js
代码如下:
// 动态加载各模块路由,./subPages/xx/routes.js
export default [].concat(...(r => {
return r.keys().map((key) => {
return r(key).map(route => {
return {
path: route.path,
name: route.name,
component: route.component,
children: route.children || []
}
})
})
})(require.context('./subPages', true, /^\.(\/\w+)+\/routes\.js$/i)))
页面动态标题
title
更新思路:
1、简单粗暴的document.title='啦啦啦啦'
2、插件 vue-router-title,直接在meta里面赋title的值。vue-wechat-title 针对微信的标题做一些兼容处理
3、使用vue的自定义指令:注册一个全局指令,v-title
举两个栗子:
// 栗子一
Vue.directive('title', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el, binding) {
document.title = el.innerText
el.remove()
}
})
<div v-title>这是标题</div>
//栗子二
Vue.directive('title', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el, binding) {
binding.value
}
})
<div v-title="这是标题"></div>
4、使用slot和组件的生命周期:写一个title
组价来更新document.title
,写起来更像在页面<title>
里面写
参考:
vue2.0 下对网页标题(document.title)更新的一种实现思路
vue-router-title
vue-wechat-title
打包遇到的问题
1、打完包(npm run build
)在预览的时候,样式和开发模式下有一些出入,对比发现css缺少了-webkit-box-orient: vertical
属性。参考:https://segmentfault.com/q/1010000009360389
因为项目中用到这个属性的地方可能比较多,懒得一个一个去修改/*! autoprefixer: off */
和/* autoprefixer: on */
,所以采取的方案是直接在webpack.prod.conf.js
中关闭了OptimizeCSSPlugin
~
一些比较细的小坑
1、因为后端的数据库没有对emoji做支持,临时将数据库的编码改成utf8mb4,时间上也来不及。所以暂时先由前端这边做处理,把用户输入的emoji表情过滤掉。用个正则/(\ud83c[\udf00-\udfff])|(\ud83d[\udc00-\ude4f])|(\ud83d[\ude80-\udeff])/
匹配,提示用户“不支持表情”。【好像在前司也处理过这个问题。。】
2、<input type="number">
,因为e也是一个数字,但是对于我们简单的输入100以内整数的操作,不需要e,所以增加个处理,也是正则匹配只能输入数字,如下:
onkeypress='return( /[\d]/.test(String.fromCharCode(event.keyCode)))'
对于用户的输入法设置为英文的时候,这个处理是ok的。但是在用户的输入法设置为中文的时候,输入dede
,再按个shift
键把中文转为英文,输入框的内容会变成ee
。所以这种处理还是存在bug的。
3、使用<input type="file">
上传文件,一般是监听change事件来做处理,但是如果前后添加的是同一文件,那么change事件不会被触发。解决的思路是在文件上传完之后清空前面的值。
推荐汇总: