vue2 vue-router2 webpack1

转自:http://www.qinshenxue.com/article/20161106163608.html;

项目地址:https://github.com/qinshenxue/vue2-vue-router2-webpack

之前写了一篇如何搭建 vue 1.x 工程的文章《vue vue-router webpack》(以下简称为“上文”),在写上文的时候,vue 2还处于beta版本,现在已经正式发布了,在其发布后不久,我就尝试着按上文的步骤来搭建 vue 2.x 的工程,结论是上文的步骤基本上没什么大的问题,只有部分细节需要调整。为了保证整体步骤的完整性,本文依旧从零开始,以一个新手视角,尽可能详尽且不遗漏的阐述每个步骤,同时又为了精简步骤,上文中的一些无关痛痒的话就省略了,对于没有变化的步骤就直接复制上文。

注意:2017-03-14 将 vue 升至 2.2.4,本文会和 vue 最新版本(仅限 2.x.x)保持一致,升级所带来的影响点会记录在升级记录章节下。

初始化工程

新建工程目录vue2practice,在目录下执行npm init来创建一个package.json,在package.json中先添加以下必备模块:

"devDependencies": {
    "vue": "^2.1.10",                                   
    "vue-loader": "^10.0.2",    // vue 组件(*.vue)的webpack模块加载器
    "vue-router": "^2.1.3",    // vue 路由插件
    "webpack": "^1.14.0",    // 模块加载器兼打包工具
    "webpack-dev-server": "^1.16.2"    // 轻量的 node.js express 服务器,用于开发调试
}

新建目录结构如下,新增的目录及文件先空着,下面的步骤会说明添加什么内容。

vue2pratice
    |-- package.json
    |-- index.html         // 启动页面
    |-- webpack.config.js  // webpack配置文件
    |-- src
        |-- components  // vue UI 组件目录
        |-- views       // vue页面组件目录
        |-- main.js     // 入口文件
        |-- router.js   // vue-router配置
        |-- app.vue     // 工程首页组件

配置webpack

webpack 默认读取 webpack.config.js,文件名不能随便改,其中 entry 是必须配置的。

module.exports = {
    entry: './src/main.js',
    output: {
        path: './dist',
        publicPath: '/dist/',
        filename: 'build.js'
    }
}

其中output的各配置项作用如下:

  • path: './dist' 打包后js、css、image等存放的目录;
  • publicPath: '/dist/' 可以不配置,如果不配置则取默认publicPath: '/',在实际项目中,静态资源一般集中放在一个文件夹下,比如static目录,那么这里就应该改成publicPath: '/static/',相应的 index.html 中引用的 JS 也要改成src="/static/build.js",publicPath 可以解释为最终发布的服务器上 build.js 所在的目录,其他静态资源也应当在这个目录下。
  • filename: 'build.js' 打包的js文件名,index.html 引用的 JS 要和这里保持一致。

配置 webpack-dev-server,只需在package.json添加以下启动命令即可。

"scripts": {
  "dev": "webpack-dev-server --inline --hot --open"
}

各命令参数作用如下:

  • --inline 一共两种模式,默认为iframe模式,inline和iframe模式最明显的区别就是访问路径的不同,iframe模式的访问路径是http://localhost:8080/webpack-dev-server/,实际上iframe模式页面嵌入的<iframe>的地址还是http://localhost:8080/,那岂不是可以直接访问,不禁想问为啥不直接访问呢?因为无论是哪种模式,都是为了做到修改代码后能自动刷新,其中iframe模式是在修改代码后,重新加载iframe,而--inline是刷新浏览器,本质上都是重新全部加载一遍
  • --hot iframe不需要配置,配置了反而不能正常刷新了,所以只能配合--inline使用,作用是开启热替换,修改代码后,浏览器只会重新加载修改的组件代码,不会全部重新加载
  • --open 自动打开浏览器

配置了server,习惯性的测试下上述配置是否成功,确保后续步骤在一个成功的基石上进行,执行npm install,安装完后执行npm run dev,浏览器会自动打开http://localhost:8080/,能访问(可以在index.html添加内容来确认是否启动成功)则说明上面的配置没问题。

Vue

新建页面

在 views 目录下新建about.vue,不像传统的页面,没有html的结构,其实就是一个vue组件,但是承载着页面的功能,后面访问/about 的就是此文件的内容。

<template>
    <div>
        这是{{page}}页面
    </div>
</template>
<script>
    module.exports = {
        data: function () {
            return {
                page: 'about'
            }
        }
    }
</script>

配置路由

vue 2.0必须配套使用vue-router 2.0,新版变化还比较大,熟悉1.0的需要再仔细看下2.0文档。新版配置更为简单,不需要先实例化后再调用map来配置(router.map({'path':{component:''}})),改为直接在实例化时传入配置(new VueRouter({ routes: [{path: '', component: ''}]})),因此可以把传入的配置提取到router.js中,方便后续配置,外部调用方式为new VueRouter(require('./router')) 。router.js内容如下:

module.exports = {
    routes: [
        {
            path: '/about',
            component: require('./views/about.vue')
        }
    ]
}

首页

首页(index.html)只需引入打包后的 js 文件(src和webpack.config的output 配置一致),#app是整个网站的挂载点,简单点说其实整个网站就是一个 vue 的实例,#app就是实例el属性值。

<body>
<divid="app"></div>
<scriptsrc="dist/build.js"></script>
</body>

接下来就要配置入口js,这个也是和vue 1.0区别比较大的地方,vue-router 1.0通过VueRouter.start(App,'#app')来初始化整个网站,这种方法在vue-router 2.0已经被废弃,因此需要我们来自己来完成new Vue()。main.js内容如下:

const Vue = require('vue');
const VueRouter = require('vue-router');
const App = require('./app.vue');
Vue.use(VueRouter);
const router = new VueRouter(require('./router'))
new Vue({
    el: '#app',
    router: router,
    render: h => h(App)
})

链接路由的不再使用<a v-link="{ path: '/about' }"></a>,改为组件的方式。app.vue内容如下:

<template>
    <div>
        <div>
            <router-linkto="/about">about</router-link>
        </div>
        <div>
            <router-view></router-view>
        </div>
    </div>
</template>

配置loader

上面添加了 vue 文件,需要在webpack.config.js中添加vue对应的loader,才能将vue文件解析成可执行的代码。

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

还记得上文提到过,这时候启动会报错,提示一些依赖的loader未安装,果然:

不同于上文的是这次只报了一个错,下面会说明原因。在package.json添加:

{
    "vue-template-compiler":"^2.1.10"
}

安装后再次启动就成功了。

上文报错提示缺少很多vue相关的loader,难道新版不需要了?查看node_modules目录,发现vue-loader所依赖的模块都已经安装了(除了vue-template-compiler是自己添加的)。

原来是vue-loader中已经将所依赖的模块放到了其目录下的package.json中的dependencies,因此不需要自己去添加了,同时也发现vue-template-complier出现在peerDependencies下,这也解释了为什么第一次启动会报缺少它的原因。如图所示:

还有在app.vue文件中使用了ES6的语法(比如:const,箭头函数)也不会报缺少babel-loader,这也归功于vue-loader新加的vue-template-es2015-compiler。不过仅仅是部分语法,比如import等语法还是需要安装babel-loader。

支持添加CSS

此时如果在about.vue文件中添加CSS代码会提示错误Cannot find module "!!vue-style-loader!css-loader!......,这是因为CSS所依赖的loader未安装,前面vue-loader已经自动将其依赖的vue-style-loader安装好了,但是vue-style-loader依赖的css-loader并没有自动安装,需要我们自己安装。在package.json中添加:

"css-loader":"^0.26.0"

安装后我们来验证一下,在about.vue中添加如下样式:

<styletype="text/css">
    .about {
        color: #f00
    }
</style>

运行效果如图所示:

只在vue文件中使用嵌入式(<style>)来添加CSS代码,是不用在webpack.config中配置loader的,可以理解为没有增加新的文件类型,背后是vue-loader利用vue-style-loader和css-loader将<style>编译成JS代码,如果想引用CSS文件的话,那意味着增加了新的文件类型(*.css),也就是说需要安装并配置文件对应的loader才能被编译成JS代码。CSS文件对应的loader为css-loader,前面已经安装,只需在webpack.config.js中增加如下配置:

{
    test: /\.css$/,
    loader: 'vue-style!css'
}

JS中引用CSS文件可以像引入JS模块一样调用require,比如在about.vue中添加require('../css/about.css');,那么about.css的样式就会被添加到页面中,自己可以做下测试。

支持添加图片等静态资源

vue模板以及CSS中免不了使用图片,现在如果直接加,又会报找不到模块的错误,静态资源(图片、图标字体、音频、视频、svg文件等)对应的loader为url-loader,loader信息及配置如下:

{
    "url-loader":"^0.5.7",
    "file-loader":"^0.9.0" // url-loader依赖file-loader
}
{
    test: /\.(jpe?g|png|gif|svg|mp3)$/,
    loader: "url"
}

支持CSS预处理语言

实际项目中大多会采用less、sass、stylus中的一种预处理语言来组织整个项目的CSS,因此需要添加这些预处理语言对应的loader,各个预处理语言的loader信息如下:

{
    "less": "^2.3.1",    // less-loader依赖less
    "less-loader": "^2.2.3",
    "node-sass": "^3.4.2",    // sass-loader依赖node-sass
    "sass-loader": "^4.0.2",
    "stylus": "^0.54.5",    // stylus-loader依赖stylus
    "stylus-loader": "^2.4.0" 
}

同CSS,如果只在vue文件中使用,则不需要配置loader,实际项目中一般只会采用一种,下面将3中常用的一起测试一下。在about.vue添加测试代码如下:

<template>
    <divclass="about">
        <divclass="test-less">测试less</div>
        <divclass="test-sass">测试sass</div>
        <divclass="test-stylus">测试stylus</div>
    </div>
</template>
<stylelang="less">
    @color: #00f;
    .test-less {
        color: @color;
    }
</style>
<stylelang="sass">
    $color: #0ff;
    .test-sass {
        color: $color;
    }
</style>
<stylelang="stylus">
    color = #f00;
    .test-stylus {
        color: color;
    }
</style>

运行效果如下:

如果想通过引用文件的方式来加载样式,就必须配置loader,配置如下:

{
    test: /\.less$/,
    loader: "vue-style!css!less"
},
{
    test: /\.scss/,
    loader: "vue-style!css!sass"
},
{
    test: /\.styl/,
    loader: "vue-style!css!stylus"
}

将about.vue中各种语言<style>节点中的样式放进一个对应文件中,然后在about.vue中引用各个文件如下:

require('../css/about.less');
require('../css/about.scss');
require('../css/about.styl');

打包发布

项目打包发布有多种选择,比如用gulp,grunt,这里就采用webpack来打包,webpack打包只需一个命令,即webpack --progress --colors,后面参数可以不要,参数是为了让编译的输出内容带有进度(--progress)和颜色(--colors)。在package.json添加如下命令:

"scripts": {
  "dev": "webpack-dev-server --inline --hot --open",
  "build":"webpack --progress --colors"
}

尝试着执行npm run build(我用的是cnpm),如图:

执行完后发现多了一个dist目录,里面只有一个build.js文件,新增的目录及文件是由webpack.config.js的output配置指定的。查看build.js,我们发现会发现几个问题:

  • 就加了一个about.vue文件,怎么build.js就233k了,虽然是未压缩版,依旧很吓人,不禁让人怀疑是不是哪里出错了,心想后面加了很多页面岂不是得好几M了。
  • js怎么压缩呢?难道还得构建完后再使用别的插件来压缩js?这么折腾人可不好。
  • 页面中添加的CSS哪里去了?怎么在build.js里面!我可不想把CSS放到js里面,怎么才能把CSS单独输出到一个文件里呢?
  • 页面中引用图片哪里去了?又在build.js里面!怎么做到的,原来是转成DataUrl了,那要是一个几百k的图片,不敢想了……

以上这些问题使用webpack插件就迎刃而解了。

使用webpack插件

什么是webpack插件?这个就类似gulp需要安装所需要的插件一样,webpack不可能做到什么事都会。虽然webpack本身就依赖各种loader,但是有些是还是loader无法完成的,这个时候就需要插件来助一臂之力了。webpack 本身内置了一些常用的插件,还可以通过 npm 安装第三方插件。

下面依次来介绍哪些插件可以解决上面的问题以及插件的使用方法。

如何使用插件

插件的使用一般是在 webpack 的配置信息 plugins 选项中指定。比如:

var webpack = require('webpack');
module.exports = {
    plugins: [
        new webpack.BannerPlugin('Hello webpack')
    ]
}

压缩JS

压缩JS用的插件为webpack.optimize.UglifyJsPlugin,uglifyjs应该比较熟悉,使用方法如下:

new webpack.optimize.UglifyJsPlugin({
    compress: {
        warnings: false
    }
})

warinings:false是在删除无用代码的时候,不显示警告,其他同类的配置可以查阅github,下面来执行看看效果。

虽然报错了,仍然输出了build.js,只是未压缩,上图错误是指在main.js第9行12列符号错误,也就是说不识别箭头函数,为啥运行可以?其实如果你去看运行的代码,会发现ES6的语法并未转换,只是Chrome支持这些语法,如果用import就会报错了。解决方法是增加babel-loader,将ES6语法进行转换,在package.json如下模块并安装:

{
    "babel-loader": "^6.2.8",
    "babel-core": "^6.18.2",
    "babel-preset-es2015":"6.18.0"
}

接着配置webpack.config.js:

{
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel'
}

上文在配置babel-loader的时候还加了很多参数,这里为什么没加?经测试这种方式(babel-loader?presets[]=es2015)没有效果,为此我还在github上提了个问题,尤雨溪(vue的作者)回答是query只适用于babel-loader,不适用于vue-loader,应该使用.babelrc。也可以在package.json中配置,两种配置方式如下:

配置好后再执行命令就不会报错了,压缩后的js大小由原来的233k到现在的90k。

提取CSS

提取CSS的插件叫extract-text-webpack-plugin,这个不是webpack自带的,需要自己安装,在package.json中添加:

{
    "extract-text-webpack-plugin": "^1.0.1"
}

配置方式如下,原来的style-loader就要去掉了,毕竟我们的目标是将CSS提取出来而不是放在head中。

var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
    module: {
        loaders: [{
                test: /\.css$/,
                loader: ExtractTextPlugin.extract("css")
            },
            {
                test: /\.less$/,
                loader: ExtractTextPlugin.extract("css!less")
            },
            {
                test: /\.scss/,
                loader: ExtractTextPlugin.extract("css!sass")
            },
            {
                test: /\.styl/,
                loader: ExtractTextPlugin.extract("css!stylus")
            },
        ]
    },
    plugins: [
        new ExtractTextPlugin("css/style.css")
    ]
}

在上面的配置中,样式被提取到了style.css文件,在index.html中添加引用后就生效了。启动发现vue文件中<style>里的样式并未纳入到style.css中,而是继续嵌入式在head中,这是由于vue-loader默认用的是vue-style-loader,想要将vue文件中的样式也提取到文件中,需要在webpack.config添加vue的loaders的配置:

module.exports = {
    module: {},
    plugins: [],
    vue: {
        loaders: {
            css: ExtractTextPlugin.extract("css"),
            less: ExtractTextPlugin.extract("css!less"),
            scss: ExtractTextPlugin.extract("css!sass"),
            stylus: ExtractTextPlugin.extract("css!stylus")
        }
    }
}

现在已经将所有的样式都提取到了一个文件中,但你会发现CSS并未压缩,看来ExtractTextPlugin的职责并不包含压缩。

PostCSS

既然上面提到CSS并未压缩,那只能去寻找其他的插件来完成这个工作,PostCSS无疑就是最佳的选择之一,当然PostCSS并不是为压缩CSS而生的,它是一个使用JS插件来转换CSS的工具, 这些插件可以支持使用变量,混入,转换未来的CSS语法,内联图片等操作。

首先安装PostCSS,webpack使用postcss-loader而不是postcss,在package.json中添加postcss-loader并安装:

"postcss-loader":"^1.2.0"

我们的首要目标是压缩CSS,既然PostCSS是依赖插件来完成特定目标的,那么我们需要先找到压缩CSS的插件,查询插件的地址为https://github.com/postcss/postcsshttp://postcss.parts/,最终发现插件cssnano可以完成我们的目标。配置PostCSS主要包括两点:

  1. 在webpack.config中注册PostCSS所需插件,vue要单独指定;
  2. 在CSS先关的loader中添加postcss,包括vue的loaders。

在package.json中添加cssnano并安装:

"cssnano":"^3.8.1"

配置如下:

module.exports = {
    module: {
        loaders: [
            {
                test: /\.css$/,
                loader: ExtractTextPlugin.extract("css!postcss")
            },
            {
                test: /\.less$/,
                loader: ExtractTextPlugin.extract("css!postcss!less")
            },
            {
                test: /\.scss/,
                loader: ExtractTextPlugin.extract("css!postcss!sass")
            },
            {
                test: /\.styl/,
                loader: ExtractTextPlugin.extract("css!postcss!stylus")
            }
        ]
    },
    vue: {
        loaders: {
            css: ExtractTextPlugin.extract("css!postcss"),
            less: ExtractTextPlugin.extract("css!postcss!less"),
            scss: ExtractTextPlugin.extract("css!postcss!sass"),
            stylus: ExtractTextPlugin.extract("css!postcss!stylus")
        },
        postcss: [require("cssnano")]
    },
    postcss: [require("cssnano")]
}

上面利用cssnano完成了压缩,其实cssnano不止是完成了压缩,还优化了你的CSS代码,比如丢弃重复的样式规则、压缩选择器、合并规则等等,更多特性可以去官网查看。就这一个插件,你可能就感受到了PostCSS的魅力,还有很多插件可以帮助你提升效率,比如autoprefixer可以自动添加浏览器前缀等,只要你想的到,就没有它做不到。

提取图片

如果是很小的图片,转成DataUrl放在JS中还是可以接受的,毕竟可以减少请求,大图肯定不适合这种方式。如果你完全不想这么做,或者只想单独把大图提出来,只需通过增加url-loader的参数即可。

{
    test: /\.(jpe?g|png|gif|svg)$/,
    loader: "url",
    query:{
        name:'images/[name].[ext]',
        limit:10000    // 单位:字节
    }
}
  • name:'images/[name].[ext]' 将符合test正则的图片都存在images目录下,[name].[ext]是文件名模板,更多占位符请参考file-loader文档
  • limit:10000 小于10kb的图片才会被转化成DataUrl,设为0并不意味着所有的图片都不被转换,如果想所有图片都不被转换,建议设为1

静态资源添加版本号

对于前后端分离的站点来讲,前端所有资源都是静态的,因此防止浏览器缓存就非常有必要。防止缓存的方法一般有两种,一种是在文件名中添加文件内容的hash值(build.xxxx.js),另外一种方法是给每个http请求加版本号参数(build.js?xxxx)。下面分别说明下两种方法如何配置。

在上一章节中提到了文件名模板占位符,添加版本号就是利用占位符[hash],而且无论哪种方式都是利用这个占位符做配置。JS、CSS 及 Image 的配置如下:

// hash可配置选项 [<hashType>:hash:<digestType>:<length>] 即 [哈希算法类型:hash:哈希摘要类型:长度]
// JS
filename: 'build.[hash:8].js'    // hash
filename: 'build.js?[hash:8]'    // query
// CSS
new ExtractTextPlugin("css/style.[hash:8].css")    //hash
new ExtractTextPlugin("css/style.css?[hash:8]")    //query
// Image
name: 'images/[name][hash:8].[ext]'    // hash
name: 'images/[name].[ext]?[hash:8]'    // query

这里很容易想到一个问题,就是打包后的CSS和JS文件名改变了,那意味着index.html中对应的引用地址也要跟着改变,但是现在关键是不知道文件名被改成啥了?(PS:虽然构建完成后显示了,如下图所示)即使知道新的文件名,每次都要手动去改index.html中引用地址也很不方便,希望最好能在打包完成后自动修改对应的引用地址,就像图片一样,CSS或页面中引用的图片自动改成了最新的文件名。下面将阐述如何通过生成首页来解决这个问题。

自动生成首页

自动生成首页需要安装webpack插件html-webpack-plugin,模块信息如下:

"html-webpack-plugin":"^2.24.1"

调用插件可以不传配置项,那么index.html会生成到output.path所指定的目录下,生成的页面会引用构建好的CSS和JS文件,但是不会包含<div id="app">。通过配置生成首页的模板(template),可以在模板中添加无法通过配置来完成的内容,还可以设置首页标题(title)、生成的目录及文件名(filename)、favicon图标(favicon)等等,所有配置参见项目github地址

// 使用默认配置
new HtmlWebpackPlugin()
// 自定义配置
new HtmlWebpackPlugin({
    title:'首页标题',
    filename:'../index.html',
    template:'index.tpl.html',
    favicon:'src/images/favicon.ico'
    ...
})

自动生成的首页只能用于发布,webpack-dev-server并不会访问生成后的首页,而且在我们开发的过程中也不需要配置诸如JS及CSS的压缩、添加版本号等,因为这些配置无疑会带来server启动变慢、占用资源等问题,解决这些问题最直接的办法就是将开发和生产得配置文件分开。

其他插件

除了上述插件外,还有一些打包时需要用到的插件,列举如下。

使用 webpack 的 DefinePlugin 来指定生产环境,以便在压缩时可以让 UglifyJS 自动删除代码块内的警告语句。

new webpack.DefinePlugin({
    'process.env': {
        NODE_ENV: '"production"'
    }
})

OccurrenceOrderPlugin 可以根据模块调用次数,给模块分配 ids,常被调用的 ids 分配更短的 id,使得 ids 可预测,降低文件大小。既然能减小打包文件体积,当然要用上。

new webpack.optimize.OccurrenceOrderPlugin()

后续

后面可以做的事还有很多,鉴于本文到这里已经很长了,于是决定将后续的配置另起一篇文章(《vue2 vue-router2 webpack(续)》)来讲解。

升级记录

vue 2.2.4

2017-03-14 将 vue 升级至 2.2.4,步骤无影响,所有升级模块信息如下:

"vue": "^2.2.4",
"vue-loader": "^11.1.4",
"vue-router": "^2.3.0",
"vue-template-compiler": "^2.2.4",
"webpack-dev-server": "^1.16.3"

vue 2.1.10

2017-01-21将vue升级至2.1.10,无影响点。所有升级模块信息如下:

"vue": "^2.1.10",    // 原2.1.9
"vue-template-compiler": "^2.1.10",       // 原2.1.9
"vue-router": "^2.1.3",       // 原2.1.1

vue 2.1.9

2017-01-17将vue升级至2.1.9,无影响点。所有升级模块信息如下:

"vue": "^2.1.9", // 原2.1.8
"vue-template-compiler": "^2.1.9" // 原2.1.8

vue 2.1.8

2017-01-10将vue升级至2.1.8,无影响点。所有升级模块信息如下:

"vue": "^2.1.8", // 原2.1.6
"vue-template-compiler": "^2.1.8" // 原2.1.6

vue 2.1.6

2016-12-20 将vue 升级至 2.1.6,无影响点。所有升级模块信息如下:

"vue": "^2.1.6",    // 原 2.1.4
"vue-template-compiler": "^2.1.6",    // 原 2.1.4
"webpack": "^1.14.0",    // 原 1.13.3

vue 2.1.4

2016-12-07 将 vue 升级至 2.1.4,无影响点。所有升级模块信息如下:

"vue-router": "^2.1.1", // 原2.1.0
"vue-template-compiler": "^2.1.4"   //  原2.1.3

vue 2.1.3

2016-12-01 将 vue 升级至 2.1.3,其他模块并未升级,升级后需要安装vue-template-compiler项目才能跑起来

posted @ 2017-04-11 10:03  Lonely,lonelyBurning  阅读(580)  评论(0编辑  收藏  举报