Vue + webpack 项目实践

最近在内部项目中做了一些基于 vue + webpack 的尝试,在小范围和同事们探讨之后,还是蛮多同学认可和喜欢的,所以通过 blog 分享给更多人。

首先,我会先简单介绍一下 vue 和 webpack:

(当然如果你已经比较熟悉它们的话前两个部分可以直接跳过)

介绍 vue

_2015_06_25_12_37_36

Vue.js 是一款极简的 mvvm 框架,如果让我用一个词来形容它,就是 “轻·巧” 。如果用一句话来描述它,它能够集众多优秀逐流的前端框架之大成,但同时保持简单易用。废话不多说,来看几个例子:

<script src="vue.js"></script>

<div id="demo">
  {{message}}
  <input v-model="message">
</div>

<script>
  var vm = new Vue({
    el: '#demo',
    data: {
      message: 'Hello Vue.js!'
    }
  })
</script>

首先,代码分两部分,一部分是 html,同时也是视图模板,里面包含一个值为 message 的文本何一个相同值的输入框;另一部分是 script,它创建了一个 vm 对象,其中绑定的 dom 结点是 #demo,绑定的数据是 {message: 'Hello Vue.js'},最终页面的显示效果就是一段 Hello Vue.js 文本加一个含相同文字的输入框,更关键的是,由于数据是双向绑定的,所以我们修改文本框内文本的同时,第一段文本和被绑定的数据的 message 字段的值都会同步更新——而这底层的复杂逻辑,Vue.js 已经全部帮你做好了。

_2015_06_24_11_00_20

再多介绍一点

我们还可以加入更多的 directive,比如:

<script src="vue.js"></script>

<div id="demo2">
  <img title="{{name}}" alt="{{name}}" v-attr="src: url">
  <input v-model="name">
  <input v-model="url">
</div>

<script>
  var vm = new Vue({
    el: '#demo2',
    data: {
      name: 'taobao',
      url: 'https://www.taobao.com/favicon.ico'
    }
  })
</script>

这里的视图模板加入了一个 <img> 标签,同时我们看到了 2 个特性的值都写作了 。这样的话,图片的 titlealt 特性值就都会被绑定为字符串 'taobao'

如果想绑定的特性是像 img[src] 这样的不能在 html 中随意初始化的 (可能默认会产生预期外的网络请求),没关系,有 v-attr="src: url" 这样的写法,把被绑定的数据里的 url 同步过来。

没有介绍到的功能还有很多,推荐大家来我(发起并)翻译的Vue.js 中文文档

web 组件化

最后要介绍 Vue.js 对于 web 组件化开发的思考和设计

如果我们要开发更大型的网页或 web 应用,web 组件化的思维是非常重要的,这也是今天整个前端社区长久不衰的话题。

Vue.js 设计了一个 *.vue 格式的文件,令每一个组件的样式、模板和脚本集合成了一整个文件, 每个文件就是一个组件,同时还包含了组件之间的依赖关系,麻雀虽小五脏俱全,整个组件从外观到结构到特性再到依赖关系都一览无余

vue 文件示例

并且支持预编译各种方言:

vue 文件示例

这样再大的系统、在复杂的界面,也可以用这样的方式庖丁解牛。当然这种组件的写法是需要编译工具才能最终在浏览器端工作的,下面会提到一个基于 webpack 的具体方案。

小结

从功能角度,template, directive, data-binding, components 各种实用功能都齐全,而 filter, computed var, var watcher, custom event 这样的高级功能也都洋溢着作者的巧思;从开发体验角度,这些设计几乎是完全自然的,没有刻意设计过或欠考虑的感觉,只有个别不得已的地方带了自己框架专属的 v- 前缀。从性能、体积角度评估,Vue.js 也非常有竞争力!

介绍 webpack

webpack 是另一个近期发现的好东西。它主要的用途是通过 CommonJS 的语法把所有浏览器端需要发布的静态资源做相应的准备,比如资源的合并和打包。

举个例子,现在有个脚本主文件 app.js 依赖了另一个脚本 module.js

// app.js
var module = require('./module.js')
... module.x ...

// module.js
exports.x = ...

则通过 webpack app.js bundle.js 命令,可以把 app.jsmodule.js 打包在一起并保存到 bundle.js

同时 webpack 提供了强大的 loader 机制和 plugin 机制,loader 机制支持载入各种各样的静态资源,不只是 js 脚本、连 html, css, images 等各种资源都有相应的 loader 来做依赖管理和打包;而 plugin 则可以对整个 webpack 的流程进行一定的控制。

比如在安装并配置了 css-loader 和 style-loader 之后,就可以通过 require('./bootstrap.css') 这样的方式给网页载入一份样式表。非常方便。

webpack 背后的原理其实就是把所有的非 js 资源都转换成 js (如把一个 css 文件转换成“创建一个 style 标签并把它插入 document”的脚本、把图片转换成一个图片地址的 js 变量或 base64 编码等),然后用 CommonJS 的机制管理起来。一开始对于这种技术形态我个人还是不太喜欢的,不过随着不断的实践和体验,也逐渐习惯并认同了。

最后,对于之前提到的 Vue.js,作者也提供了一个叫做 vue-loader 的 npm 包,可以把 *.vue 文件转换成 webpack 包,和整个打包过程融合起来。所以有了 Vue.js、webpack 和 vue-loader,我们自然就可以把它们组合在一起试试看!

项目实践流程

回到正题。今天要分享的是,是基于上面两个东西:Vue.js 和 webpack,以及把它们串联起来的 vue-loader

Vue.js 的作者以及提供了一个基于它们三者的项目示例 (链接已失效)。而我们的例子会更贴近实际工作的场景,同时和团队之前总结出来的项目特点和项目流程相吻合。

目录结构设计

  • <components> 组件目录,一个组件一个 .vue 文件
    • a.vue
    • b.vue
  • <lib> 如果实在有不能算组件,但也不来自外部 (tnpm) 的代码,可以放在这里
    • foo.css
    • bar.js
  • <src> 主应用/页面相关文件
    • app.html 主 html
    • app.vue 主 vue
    • app.js 通常做的事情只是 var Vue = require('vue'); new Vue(require('./app.vue'))
  • <dist> (ignored)
  • <node_modules> (ignored)
  • gulpfile.js 设计项目打包/监听等任务
  • package.json 记录项目基本信息,包括模块依赖关系
  • README.md 项目基本介绍

打包

通过 gulpfile.js 我们可以设计整套基于 webpack 的打包/监听/调试的任务

gulp-webpack 包的官方文档里推荐的写法是这样的:

var gulp = require('gulp');
var webpack = require('gulp-webpack');
var named = require('vinyl-named');
gulp.task('default', function() {
  return gulp.src(['src/app.js', 'test/test.js'])
    .pipe(named())
    .pipe(webpack())
    .pipe(gulp.dest('dist/'));
});

我们对这个文件稍加修改,首先加入 vue-loader

tnpm install vue-loader --save

.pipe(webpack({
  module: {
    loaders: [
      { test: /\.vue$/, loader: 'vue'}
    ]
  }
}))

其次,把要打包的文件列表从 gulp.src(...) 中抽出来,方便将来维护,也有机会把这个信息共享到别的任务

var appList = ['main', 'sub1', 'sub2']

gulp.task('default', function() {
  return gulp.src(mapFiles(appList, 'js'))
    ...
})

/**
 * @private
 */
function mapFiles(list, extname) {
  return list.map(function (app) {return 'src/' + app + '.' + extname})
}

现在运行 gulp 命令,相应的文件应该就打包好并生成在了 dist 目录下。然后我们在 src/*.html 中加入对这些生成好的 js 文件的引入:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Main</title>
</head>
<body>
  <div id="app"></div>
  <script src="../dist/main.js"></script>
</body>
</html>

用浏览器打开 src/main.html 这时页面已经可以正常工作了

加入监听

监听更加简单,只要在刚才 webpack(opt) 的参数中加入 watch: true 就可以了。

.pipe(webpack({
  module: {
    loaders: [
      { test: /\.vue$/, loader: 'vue'}
    ]
  },
  watch: true
}))

当然最好把打包和监听设计成两个任务,分别起名为 bundlewatch

gulp.task('bundle', function() {
  return gulp.src(mapFiles(appList, 'js'))
    .pipe(named())
    .pipe(webpack(getConfig()))
    .pipe(gulp.dest('dist/'))
})

gulp.task('watch', function() {
  return gulp.src(mapFiles(appList, 'js'))
    .pipe(named())
    .pipe(webpack(getConfig({watch: true})))
    .pipe(gulp.dest('dist/'))
})

/**
 * @private
 */
function getConfig(opt) {
  var config = {
    module: {
      loaders: [
        { test: /\.vue$/, loader: 'vue'}
      ]
    }
  }
  if (!opt) {
    return config
  }
  for (var i in opt) {
    config[i] = opt
  }
  return config
}

现在你可以不必每次修改文件之后都运行 gulp bundle 才能看到最新的效果,每次改动之后直接刷新浏览器即可。

调试

_2015_06_25_12_45_41

打包好的代码已经不那么易读了,直接在这样的代码上调试还是不那么方便的。这个时候,webpack + vue 有另外一个现成的东西:source map 支持。为 webpack 加入这个配置字段 devtool: 'source-map'

var config = {
module: {
loaders: [
{ test: /.vue$/, loader: ‘vue’}
]
},
devtool: ‘source-map’
}

再次运行 gulp bundlegulp watch 试试看,是不是开发者工具里 debug 的时候,可以追踪断点到源代码了呢:)

_2015_06_25_12_43_45

完整的 javascript 代码如下:

var gulp = require('gulp')
var webpack = require('gulp-webpack')
var named = require('vinyl-named')


var appList = ['main']


gulp.task('default', ['bundle'], function() {
  console.log('done')
})

gulp.task('bundle', function() {
  return gulp.src(mapFiles(appList, 'js'))
    .pipe(named())
    .pipe(webpack(getConfig()))
    .pipe(gulp.dest('dist/'))
})

gulp.task('watch', function() {
  return gulp.src(mapFiles(appList, 'js'))
    .pipe(named())
    .pipe(webpack(getConfig({watch: true})))
    .pipe(gulp.dest('dist/'))
})


/**
 * @private
 */
function getConfig(opt) {
  var config = {
    module: {
      loaders: [
        { test: /\.vue$/, loader: 'vue'}
      ]
    },
    devtool: 'source-map'
  }
  if (!opt) {
    return config
  }
  for (var i in opt) {
    config[i] = opt[i]
  }
  return config
}

/**
 * @private
 */
function mapFiles(list, extname) {
  return list.map(function (app) {return 'src/' + app + '.' + extname})
}

最后,杜拉拉不如紫罗兰

做出一个 vue + webpack 的 generator,把这样的项目体验分享给更多的人。目前我基于团队内部在使用的轻量级脚手架工具写了一份名叫 just-vue 的 generator,目前这个 generator 还在小范围试用当中,待比较成熟之后,再分享出来

总结

其实上面提到的 just-vue 脚手架已经远不止文章中介绍的东西了, 我们在业务落地的“最后一公里”做了更多的沉淀和积累,比如自动图片上传与画质处理、rem单位自动换算、服务端/客户端/数据埋点接口的梳理与整合、自动化 htmlone 打包与 awp 发布等等。它们为支持业务的开发者提供了更简单高效的工作体验。 篇幅有限,更多内容我也希望将来有机会再多分享出来。

最后再次希望大家如果有兴趣的话可以来玩一下,无线前端组内的同学我都愿意提供一对一入门指导:)

Just Vue!

向本文提出修改或勘误建议

早期评论

  • hilooooo 2015/06/25 03:18:22

    是真爱
  • szmtcjm 2015/06/26 01:08:22

    为什么我们内部项目这么少!
  • 傅小黑 2015/06/27 09:04:28

    还是不太喜欢require(css)哈哈
  • 囧克斯 2015/06/27 12:00:47

    你看我也纠结过这个问题,现在也逐渐接受了 http://weibo.com/1712131295/ClWOljrho
  • Randy 2015/11/09 11:55:17

    用多了之后你会发现 require css 是很方便的东西,因为用 style-loader 和其它 loader 可以让你免于写 gulp 去 process 你的 stylesheet。
  • 商店 2018/01/26 02:15:50

  • test 2018/04/17 02:45:52

    的所发生的?汉字多日本字少
  • dazhenhan 2015/07/15 05:05:42

    发现一个小错误,在getConfig方法中,for 中取值漏了i,应为

    for (var i in opt) {
    config[i] = opt[i]
    }

    是吧?
  • think2011 2015/07/23 12:17:17

    好大的版面文字
  • 今天使用Vue+webpack对项目进行了重构 丨 Raito_MH的世界 2015/08/05 12:12:26

    [...]在这几个月接触的项目中,都使用vue.js这个框架来进行开发,可以说是各种便利,然后今天无意间在微博上看到了勾股大大的这篇博文《Vue + webpack 项目实践》,可以说是受益匪浅,于是对目前正在做的web版本进行了重构。[...]
  • tcdona 2015/08/05 04:38:37

    报错了,请问谁成功了吗
  • tcdona 2015/08/05 05:10:03

    发现是我项目文件的问题,config 错误确实存在。

    最后希望能直接提供zip 包demo,避免一些错误~

    文章赞!
  • 淘宝无线前端图片处理流程 | 潮流前端 2015/08/10 04:38:33

    [...]我在这个过程中,融入了之前一段时间集中实践的 vue 和 webpack 的工程体系,在 vue 的基础上进行组件化开发,在 webpack 的基础上管理资源打包、集成和发布,最终合并在了最新的 just-vue 的 adam template 里面。[...]
  • naux 2015/08/14 11:50:42

    .vue 文件里的css经过webpack都是inline style,如何把vue组件样式抽取出来,合并到一个css文件中
  • 左手阳光 2016/07/01 11:59:04

    使用var ExtractTextPlugin = require("extract-text-webpack-plugin");
  • 手机淘宝前端的图片相关工作流程梳理! | 加速会 2015/08/23 04:54:10

    [...]我在这个过程中,融入了之前一段时间集中实践的 vue 和 webpack 的工程体系,在 vue 的基础上进行组件化开发,在 webpack 的基础上管理资源打包、集成和发布,最终合并在了最新的 just-vue 的 adam template 里面。[...]
  • 梁晓晨 2015/09/02 03:26:02

    有了webpack还配合gulp。我也是醉了
  • 测试用户 2015/09/07 10:25:57

    “里面包含一个值为 message 的文本何一个相同值的输入框”
    “何”to“和”。
  • Aaron 2015/09/19 09:55:57

    我个人也尝试了vue-gulp-webpack的方案
    为什么用gulp
    配合browser-sync做代码注入与浏览器刷新
    gulp watch监控文件变动
    webpack提供监控,但是很慢,消耗比较大
    提供一个简单的配置:
    https://github.com/JsAaron/vue-gulp-webpack-example
  • ivy 2015/11/29 04:35:58

    值得学习
  • 雨时节 2016/02/25 12:23:19

    网址已收藏,谢谢。
  • Vue + webpack 项目实践 R11; 思维谷 2016/04/06 06:44:02

    [...]本博客转载于http://jiongks.name/blog/just-vue/[...]
  • zjb 2016/04/09 02:46:18

    最近也在做gulp+webpack+vue,但是每次上线时,打包时间好长啊~要20几秒~
  • genffy 2016/04/09 09:44:13

    20几秒你就偷着乐吧,grunt那东西上个线concat minify 要十几分钟,不过跟用了requirejs有关,还有我们是all in one,准备改写,实在受不了了。
  • zjb 2016/04/10 05:59:00

    10几分钟 怎么忍啊!!
  • fanhehe 2016/06/10 06:58:00

    学习了!!!
  • 小错 2016/06/17 09:51:46

    请问不同的组件之间怎么传数据呢?
  • Simon 2016/06/23 02:19:22

    为什么中间还需要到gulp来处理,不直接webpack+vue呢?
  • 喵喵 2016/06/30 10:09:34

    说好的webpack ,怎么还用gulp呢
  • Sunny 2016/07/18 04:51:43

    使用 gulp的原因是什么呢?难道是广泛说的 webpack watch 占内存太高?
  • LazyCat 2017/02/20 12:36:54

    webpack能做图片压缩吗,雪碧图行么
  • LazyCat 2017/02/20 12:36:54

    webpack能做图片压缩吗,雪碧图行么
  • liang 2016/07/25 05:03:47

    v-attr这个修饰符在1.x已经不可用了
  • Gao 2016/07/25 05:49:31

    标题应该是为“Vue+gulp-webpack”
  • Mr.D 2016/07/26 08:08:08

    求问~~编辑器主题是啥哈哈 看着舒服
  • liangdaren 2016/08/05 04:39:06

    sublime吧
  • 我额你的啊 2017/05/31 11:51:51

    是ATOM的默认主题
  • Leisure 2016/08/18 09:47:08

    有源代码吗
  • Vue.js入门参考资料 R11; 鹏仔 2016/08/29 06:12:23

    [...]Vue + webpack 项目实践[...]
  • zonghua 2016/10/05 01:45:04

    一个页面就对应一个 vue 示例吗??数据的嵌套有什么方法,我很纠结这个
  • 夜深未眠 2016/11/21 12:55:33

    感谢楼主的分享。
  • tolerious 2016/11/21 02:32:49

    已被这么大的文字亮瞎,哈哈。
  • wolf 2016/11/29 03:45:53

    项目的实例404
  • 小七 2016/11/30 11:52:02

    项目实例的链接失效啦。在vue作者的github找到一个demo
    连接是: https://github.com/vuejs/vue-loader-example
  • 囧克斯 2017/06/27 04:38:18

    感谢指正,我加了实效注明
  • 你妈你爸 2017/02/09 03:09:23

    国际化
  • zhuzhuxia216 2017/07/11 07:35:45

    大牛犀利,言语中,作者对技术的荒蛮生长的各方面东西,保持谨慎接受。
  • 小智 2017/08/07 01:34:43

    我们刚出了课程,欢迎大家帮忙瞅瞅。

    vue.js 2:
    http://xc.hubwiz.com/course/592ee9b2b343f27b0ae1ba99

    vuex 2
    http://xc.hubwiz.com/course/597d463fff52d0da7e3e397a
  • 酸奶不是酸奶 2017/09/12 03:52:12

    感觉用gulp搞得好复杂 按理说 一般webpack都可以的
  • VueJS学习资料大全 | Worktle.com 2017/11/24 11:28:55

    [...]Vue + webpack 项目实践[...]
  • 释然了 2018/03/05 05:32:37

    请问热启动如何加载?
  • ezpod 2018/04/11 11:42:11

    推荐一个vue工程化实践教程:http://xc.hubwiz.com/course/598bad66c7fd1d49453979c9?affid=jks7878
  • qiuguomuhai 2018/05/16 04:35:12

    “杜拉拉不如紫罗兰”是什么梗?
  • 囧克斯 2018/05/23 10:19:12

    好吧都过去这么久了。。。我揭晓答案吧:“独乐乐不如众乐乐”的拼音推荐结果 (就是当时我用拼音输入法写 dllbrzll 的第一个推荐词) 不好意思这个有点冷 - -
  • 前端資源分享-只為更好前端 | 程式前沿 2018/05/21 06:04:12

    [...]Vue webpack 專案實踐[...]
  • Vue.js资源分享 - 奇奇问答 2018/06/13 02:32:33

    [...]Vue + webpack 项目实践[...]
  • 搭建vue+webpack+mock脚手架(一) - 奇奇问答 2018/06/13 02:39:14

    [...]vue+webpack项目实战 http://jiongks.name/blog/just…[...]