2

Vue.js 和 Webpack

 3 years ago
source link: https://lutaonan.com/blog/vue-and-webpack/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

转载前请务必先联系邮箱

最近在把 SISE Game(我们学校的校内游戏直播网站) 从原本的 Ruby on Rails 彻底用 Node.js 重写, 经过一些考虑,决定用 Vue.js 和 Express.js 实现前后端分离的架构,在这几天的重写过程中,积累了对 Webpack 和 Vue.js 的一些新的看法。

About Vue.js

Vue 是个很年轻的 MVVM Library,常常有很多人用 Angular 和 Vue 比较,因为两者都是 MVVM,但实际上,前者是 Framework,而后者是 Library。前者有很陡峭的学习曲线,后者可以很快地掌握运用到项目中去。

Vue 的官方是用 a library for building modern web interfaces 来描述自己的。Vue 适合和 React 对比,因为在使用 Vue 的 Components System 开发比较大型的 Single Page Application 的时候,我发现它和 React 有一些相似的地方。如果你赞同 React 的思想,但又不想写 JSX,那么,你就可能需要试试 Vue 了。

一个用 Vue 实现 Data binding 的 Demo:

<!-- index.html -->
<div id="#app">
<input v-model="msg" />
<p></p>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
msg: 'hello Vue.js'
}
})
</script>

Why Vue.js

前端开发发展到现在,我们做过的很多努力,都是在尝试把开发者从繁琐的 DOM 操作和管理 DOM state 中解放出去,我们希望只需要通过描述数据和行为,DOM 自己就可以发生对应的变化,React 在 View 这一层实现了这一目标,而 MVVM 则是通过 ViewModel 的 Data Binding。React 宣称自己是 View,那么在我看来, Vue 则是 View + ViewModel,并且 Vue 更加 lightweight 和 flexible。

Vue 最让我喜欢的是它的 Components System,利用它可以构建组件化的中大型应用。React 当然也是组件化的,但是 Virtual DOM (JSX) 在一些场景让我很不满意。比如有一次,我用一个使用 React 的项目中,想要在一 <video> 里使用 webkit-playsinline 这个 attribute,但是 React 不支持,渲染的时候直接被 ignored,我必须手动地操作 DOM 给 <video> setAttribute。相反,Vue 的 Components System 当中,写的是真正的 DOM,不需要担心不支持不兼容的各种情况。

容易被用作对比的是 Angular。我第一次听说 Vue 的时候,也是把它当作一个 lightweight 的 Angular alternative. 但是当真正实践使用 Vue 的时候,才发现它和 Angular 有着很大的不同。Angular 是一个 Framework,一旦你使用它,就必须按照它的一套去组织你的项目。以前写 Angular 项目的过程和经历对我个人来说都不太愉快,我更加倾向于 Vue 这种更灵活的方案。

关于 Vue 和其它库和框架的对比,官方也有作者更详细的 解答中文版本

Using Vue.js

SISE Game 并不算一个大型的 Web APP,但也规范地使用组件化的开发,整个项目的结构大致如下:

├── app
│ ├── app.js #entry
│ ├── app.vue
│ ├── config.js
│ ├── filters # 自定义的一些 filters
│ ├── components #各种组件
│ ├── models
│ ├── utils
│ └── views #各种页面的 views
│ ├── home.vue
│ ├── room.vue
│ ├── signin.vue
│ ├── signup.vue
│ └── user.vue
├── bower.json
├── build
├── gulpfile.js
├── index.html
├── node_modules
├── package.json
├── static #静态文件
│ ├── images
│ ├── styles
│ └── swfs
└── webpack.config.js

Vue 通过自己的 .vue 文件来定义 components,.vue 文件里包含组件的模板、逻辑和样式,从而实现组件和组件之间的分治,非常易于维护。

<!-- components/user.vue -->
<template>
<p>Hello </p>
<button v-on="click: alertName()">alert!</button>
</template>

<script>
module.exports = {
data: function(){
return {
name: 'Randy'
}
},
methods: {
alertName: function(){
alert(this.name);
}
}
}
</script>

<style>
p{
color: #69C;
}
</style>

以上就是一个简单的 component 实现,借助 webpack,甚至可以直接在 component 里写 es6、scss 和 jade。

路由对于 Single Page Application 来说应该算是不可少的东西,Vue 作为一个 Library,它本身并不提供这些组件。目前官方的 vue-router 仍处于 technical preview 的状态,官方也建议可以使用 component 和 Director.js 实现路由,比如:

<div id="app">
<component is=""></component>
</div>
Vue.component('home', { /* ... */ })
Vue.component('page1', { /* ... */ })
var app = new Vue({
el: '#app',
data: {
currentView: 'home'
}
})
// Switching pages in your route handler:
app.currentView = 'page1'

这样你只需要操作 app.currentView 的值就可以实现视图的切换,这一步通常会配合 Director.js 这类的 hash router.

About Webpack

与其费周章说明 Webpack 是什么东西,倒不如先说说不用 Webpack 以前的一些现实。

我们在写前端 JavaScript 的时候,通常是写在多个 .js 文件里,通过闭包避免全局变量污染,然后一股脑地用 <script> 引入。

<body>
...
<script src="a.js"></script>
<script src="b.js"></script>
<script src="c.js"></script>
</body>

出于性能上的追求,我们会应该把 a.js b.js c.js 合并为同一个文件 bundle.js 来减少请求数量,变成:

<body>
...
<script src="bundle.js"></script>
</body>

使用 Gulp/Grunt 等自动化构建工具很容易可以实现这样的 concat,但是很快我们就会发现,单纯的 concat 并不是一个好的方案,因为代码文件之间的依赖关系不明确,这样一来,有时不得不花一些时间去组织 concat 的顺序。我们很希望像写 Node 一样模块化地去写前端 JavaScript。

又有些时候,在两个不同的页面当中我们常常会共用一些代码,单纯的 concat 会增加很多不必要的体积。

所以 ,我们理想的情况是,可以在前端优雅地写符合模块规范(AMD, UMD, CommonJS)的代码并且自动打包,最好还能自动把重用的文件分离出来。

嘿,Webpack 就很擅长做这种事。

Using Webpack

Webpack 兼容所有模块规范(如果你不知道到底用哪一种,就用 CommonJS)。

配置 webpack 比较简单,你需要定义入口文件和 bundle 输出的目录:

// webpack.config.js
module.exports = {
entry: './app.js',
output: {
path: './build',
filename: 'bundle.js'
}
}

这样,你就能在前端这样去写 JavaScript:

// /app.js
var Vue = require('vue');
var app = new Vue({/*...*/})

这是 CommonJS 的写法,如果你写过 Node.js,应该对这种写法相当熟悉。这时运行 $ webpack ,webpack 会自动根据入口文件 app.js 中的依赖关系来打包成单个 js 文件,输出到配置文件中指定的 output path 中。

webpack 也可以通过 plugin 自动分析重用的模块并且打包成单独的文件:

// webpack.config.js
var webpack = require('webpack'),
CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin

module.exports = {
entry: './app.js',
output: {
path: './build',
filename: 'bundle.js'
},
plugins: [
new CommonsChunkPlugin('vendor.js')
]
}

多入口文件

webpack 的一个特色是可以指定多个入口文件,最后打包成多个 bundle。比如说 Timeline page 和 Profile page 是不同的页面,我们不希望两个页面的 js 被打包在一起,这时我们就可以为 timeline 和 profile 两个页面定义不同的入口:

// webpack.config.js
module.exports = {
entry: {
timeline: './timeline.js',
profile: './profile.js'
},
output: {
path: './build',
filename: '[name].bundle.js'
},
plugins: [
new CommonsChunkPlugin('vendor.js')
]
}

最后会被分别打包成 timeline.bundle.jsprofile.bundle.js

loader

webpack 神奇的地方在于,任何的文件都能被 require()。依靠各种 loader,使你可以直接 require() 样式、图片等静态文件。这些静态文件最后都会被处理(比如 scss pre-process 和图片的压缩)和打包在配置好的 output path 中。

#container{
background-image: url(require('.https://gbstatic.djyde.com/blog/background.png'));
p{
color: #69C;
}
}
// app.js
require('./styles/app.scss')
// blablabla....

你可以像上面这样在 JavaScript 中引入 scss (和在样式中引入图片),只要你配置好处理 scss 的 loader:

module.exports = {
entry: './app.js',
output: {
path: './build',
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.(css|scss)$/,
loader: ExtractTextPlugin.extract('style','css!sass')
},
{
test: /\.(png|jpg)$/,
loader: 'url?limit=8192' // 图片低于 8MB 时转换成 inline base64,非常神奇!
}
]
}
}

css 默认被编译到 JavaScript 中成为字符串后再被插入到 <style> 中,我个人建议使用 ExtractTextPlugin 这个插件把 css 分离出去。

Why Vue.js + Webpack

在以往的一些小型的前端项目中,我习惯把逻辑(scripts)、视图(views)和样式(styles)分开在独立的目录当中,保证三者不耦合在一起。但是随着项目越来越大,这样的结构会让开发越来越痛苦,比如要增加或修改某个 view 的时候,就要在 scriptssytles 里找到对应这个 view 的逻辑和样式进行修改。

为了避免这样随着项目增大带来的难于维护,我开始尝试前端组件化,把 views 拆分成不同的组件(component),为单个组件编写对应的逻辑和样式:

app/components
├── Chat
│   ├── Chat.jade
│   ├── Chat.js
│   └── Chat.scss
└── Video
    ├── Video.jade
    ├── Video.js
    └── Video.scss

这样的开发模式,不仅提高代码的可维护性和可重用性,还有利于团队之间的协作,一个组件由一个人去维护,更好地实现分治。幸运的是,随着 React 越来越火,组件化的开发模式也就越来越被接受。

Using Vue.js + Webpack

在 Vue 中,可以利用一个 .vue 文件实现组件化,而不需要对每个组件分别建立 style, scripts 和 view。这样做的好处是使组件能更加直观,而坏处是目前有些 editor 对 .vue 的语法支持还是不太好。我用 Atom 写 .vue 的时候,<style> 的那一块并不能自动补全。不过我个人不依赖 css 的补全,所以没有太大的影响。如果你比较依赖这个,建议你还是把这些代码分离出来。

一个简单的 Vue Component:

<!-- components/sample.vue -->

<template lang="jade">
.test
h1 hello
</template>

<script>
module.exports = {
el: '#app',
data: {
msg: 'world'
}
}
</script>

<style lang="sass">
.test{
h1{
text-align: center;
}
}
</style>

我们使用 Webpack 就可以自动将 .vue 文件编译成正常的 JavaScript 代码,只需要在 Webpack 中配置好 vue-loader 即可:

// webpack.config.js

module.exports = {
entry: './app.js',
output: {
path: './build',
filename: 'app.js'
},
module: {
loaders: [
{
test: /\.vue$/, loader: 'vue-loader'
}
]
}
}

这样,就可以正常地在文件中 require() 所有 .vue 文件:

module.exports = {
el: '#app',
data: {/* ... */},
components: {
'sample': require('./components/sample.vue')
}
}

css 分离

vue-loader 使用 style-loader 把 component 当中的样式编译成字符串后插入到 <head> 中去。但我们希望把 css 文件独立出去,那么可以使用上一篇文章提到的 ExtractTextPlugin 插件,配合 vue-loaderwithLoaders() 方法实现生成独立样式文件:

// webpack.config.js
var vue = require('vue-loader')
, ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
entry: './app.js',
output: {
path: './build',
filename: 'app.js'
},
module: {
loaders: [
{
test: /\.vue$/, loader: vue.withLoaders({
sass: ExtractTextPlugin.extract("css!sass") // 编译 Sass
})
}
]
},
plugins: [
new ExtractTextPlugin('app.css') // 输出到 output path 下的 app.css 文件
]
}

webpack 是一个十分好用的模块打包工具,使用它更加利于实现前端开发工程化。

很多人认为 webpack 可以取代 Gulp/Grunt 等构建工具,其实不然。Webpack 仅仅是顺便替构建工具分担了一些预编译预处理的工作,而构建工作不仅仅只有预编译啊。

Vue.js 和 Webpack 的结合使用方法写到这里就已经算是写完了,当然,还有很多其它的实践方法,都要靠读者自己去摸索了,这个系列仅仅是想给没有使用过 Vue.js 或者 Webpack 的读者一个大概的认识。

最后趁这个机会感慨一下,前端开发是让人感到兴奋的,我以前也写很多有关前端的东西,但从来不愿意称自己为『前端开发者』,是由于自己对前端开发的各种浅见,认为前端开发低端、repetitive、不能成大事。但是经过更加深入的实践,才慢慢发现前端也是工程化的、有学问的、有活力的。我很高兴可以作为一名『前端开发者』,在这里感受日新月异的氛围的技术浪潮。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK