webpack构建vue项目(再谈配置)

webpack配置起来确实麻烦,这不,之前用刚配好了vue1+的版本,结果在(部分)安卓机上测试,发现存在开启热加载(dev-server)的情况下不能识别vue语法的问题,试了很多方法,都没能很好的解决,最后索性将vue升级到2+,竟然就能识别了,好吧!

1.先分享一下webpack配置vue2+的一些不同(本人亲测):

(1)dependencies中的vue默认安装2+,直接运行,会报如下错:[Vue warn]: Failed to mount component: template or render function not defined。
如果dependencies中的vue选择^1.0.26,那么devDependencies中对应的vue-loader最好选择^7.3.0,vue-hot-reload-api最好选择^1.2.0,否则就会报错。
(2)如果vue选择安装1+,dependencies中的vue-router最好选择^0.7.13(默认安装2+,无法识别router.map()这个方法)。
(3)如果vue选择安装1+,dependencies中的vue-validator最好选择^2.0.0(默认安装2.1.7)。
(4)如果vue选择安装1+,后面在开启webpack dev server的时候,处于同一内网中的安卓手机访问本地设备的输出页面会出现不识别vue语法的兼容问题,ios手机可以正常访问和解析,但是开启别的server再来访问并不会出现这种兼容问题,所以为了测试方便,建议vue选择安装2.0的版本。
(5)如果vue选择安装2+,vue2.0有两种构建模式,默认情况下运行构建,但是不能解析单文件的template模板,所以要使用独立构建,需要在alias中指定vue$的模块别名地址,即

// 其他解决方案
resolve: {
    // require时省略的扩展名,遇到.vue结尾的也要去加载
    extensions: ['','.js', '.vue'],
    // 模块别名地址,方便后续直接引用别名,无须写长长的地址,注意如果后续不能识别该别名,需要先设置root
    root:"../node_modules",
    alias:{
        'vue$':'vue/dist/vue.js'
    }
}, 

(6)如果vue选择安装2+,对应的vue-validator就必须安装3+,否则会报错,但是这样一来在开启webpack dev server的时候,又会出现安卓手机不识别vue-validator的兼容问题,ios手机可以正常访问和解析,所以为了测试方便,改用其他的基于vue的表单验证插件,即vuerify,需要引入vue2+、vuerify和v-vuerify-next。
(7)如果vue选择安装2+,涉及到的loader尽量升级到最新,否则会报错;最好是将vue升级到2.0.7,对应的vue-loader升级到8.5.4,vue-html-loader升级到1.2.3,vue-hot-reload-api升级到2.0.6。
(8)如果vue选择安装2+,对应的router要升级到2+,但是这时之前使用的表单验证插件就会做出问题(例如:vue-validator-3.0.0-alpha.1和vuerify+v-vuerify-next这两款插件在路由跳转的时候都会报错,尝试了很多方法都无济于事,感觉还是版本匹配的问题),awesome-vue集合了来自社区贡献的数以千计的插件和库,在这里我找了一些专门针对vue2+的表单验证插件,发现Vee-Validate和Vue-Easy-Validator这两款插件没有出现之前遇到的问题,而且前者的英文文档写得相当详细,赞之。

2.不要小瞧版本匹配问题,webpack自带插件和第三方插件,vue和里面需要引入的插件,再加上各种模块加载器,有时匹配对了一个,另一个又会出问题,我就是在这里浪费了太多的时间,最后索性跟着版本走,强势拥抱2+。关于vue2+的语法,官网里写得很详细,这里就不赘述,我还是接着讲webpack配置问题吧:

(1)配置文件里的入口和出口:

// 入口文件,路径相对于本文件所在的位置,可以写成字符串、数组、对象
entry: {
    // 以下是单页面的入口路径
    index: path.resolve(__dirname, "../entry/index.js"),

    // 需要被提取为公共模块的群组
    vendors: ["vue","vue-router","vue-resource","vee-validate","jquery"],
},

// 输出配置
output: {
    // 输出的js文件,路径相对于本文件所在的位置
    path: path.resolve(__dirname, "../output/js/"),

    // 将入口文件中涉及到的同步加载的js文件打包成一个js文件,基于文件的md5生成hash名称的script来防止缓存
    filename: "[name].[hash].js",

    // 异步加载的业务模块,例如按需加载的.vue单文件组件
    chunkFilename: "[id].[name].[chunkHash].js"
}

这里需要注意的点不少,我主要说两个:

一个是publicPath,上次说过测试环境里写成 config.output.publicPath = "/",不建议在生产环境里动它,但最近导出文件的时候如果不设置publicPath,按需加载的.vue单文件组件中的script路径会报错,所以还是得设置一下publicPath,其路径可以写成相对于生成的html单文件所在位置的相对路径;

另一个就是chunkFilename,上次没怎么提它,主要是没怎么用到它,如果项目里涉及到异步加载的业务模块,就不得不提它了。如果使用AMD风格的requireJS来实现路由组件的懒加载,例如:

const Register = resolve => require(["../src/private/components/register"],resolve);

这样写的话,这个组件就不会和entry中引入的js文件一起打包,而是单独打包成一个js文件,名字就是这里的chunkFilename,带一个自动分配的,可读性很差的[id]。如果想在命名的时候更有归属感,即带上一个[name],可以使用require.ensure来实现路由组件的懒加载,例如:

const Register = resolve => require.ensure(["../src/private/components/register"], () => resolve(require("../src/private/components/register")), "register");

如果要把某个路由下的所有组件都打包在同个异步chunk中,无须明确列出require.ensure的依赖,即传空数组就行。如果你还想在按需加载某个模块的同时执行一些代码,可以写成:

const Register = resolve => {
    require.ensure(["../src/private/components/register"], () => {
        // 这里可以写异步加载指定模块之前的代码
        resolve(require("../src/private/components/register"));
        // 这里可以写异步加载指定模块之后的代码
    },"register")
}

(2)在测试环境中写了 config.output.publicPath = "/" 之后,当前配置文件下的很多相对路径都是相对于这个来设定,即很多涉及到相对路径的地方需要发生相应的变化,否则开启dev-server之后会报错找不到文件的错误,那么有哪些地方需要改呢,我个人建议改以下几个地方:

某些模块加载器的路径,例如加载图片的url-loader和加载图标的file-loader;

某些插件的路径,例如生成单个html文件的HtmlWebpackPlugin,提取css单文件的ExtractTextPlugin

(3)为了防止“找不到favicon.ico文件”这种错误带来的干扰,找一张图塞到项目根目录下,输出的时候直接在 new HtmlWebpackPlugin 插件参数列中写 favicon: "favicon.ico",然后开启dev-server就不会报错啦,但是生产环境下还是会报错,原因是导出的位置和输出的js文件同级,所以得把它重新塞到和输出的html文件同级,这里我是用的CopyWebpackPlugin这款插件(第三方插件),代码如下:

// 把指定的文件复制到指定的目录
new CopyWebpackPlugin([
    // from写的是源文件名,这里的位置是在项目根目录下,to是写将要复制过去的目录位置,相对于输出的js文件
    {from:'favicon.ico',to:"../html/favicon.ico"}
])

(4)对于.vue单文件,css默认是内部样式,现在要把它里面的css提取出来变成外部导入,但是如果.vue单文件组件是按需加载,那么此设置无效,即会重新变回内部样式(也可能是我自己弄错了),代码如下:

vue: {
    loaders: {
        css: ExtractTextPlugin.extract('vue-style-loader', 'css-loader'),
    }
}

(5)config.devtool这个就看自己喜好吧,开发环境下推荐使用cheap-module-eval-source-map,生产环境下推荐使用cheap-source-map或source-map,后者得到的.map文件体积比较大,但是能够完全还原以前的js代码

(6)如果要提取入口文件里面的公共模块,配置文件中必须要有以下三步:

entry: {
        // path.resolve([from ...], to) 将to参数解析为绝对路径
        index:path.resolve(__dirname, '.index.js'),
        // 需要被提取为公共模块的群组
        vendors:['vue','vue-router','jquery']
},
new webpack.optimize.CommonsChunkPlugin({
        name: 'vendors',
        filename: 'vendors.js',
}), 

第三步是 new HtmlWebpackPlugin 插件参数列里的chunks里一定要引入vendors;

(7)如果将css单独提取出来,配置文件中必须要有以下三步:

var ExtractTextPlugin = require('extract-text-webpack-plugin');
// module.loaders里添加
{
      test: /\.css$/,                  
      // loader: 'style-loader!css-loader',
      // 将样式抽取出来为独立的文件
      loader: ExtractTextPlugin.extract("style-loader", "css-loader"),
      exclude: /node_modules/
}
new ExtractTextPlugin("../css/[name].[contenthash].css")

3.其他需要注意的地方:

(1)html文件中最好只写和html相关的标签语言,尽量不要导入外部的css或者js文件,也尽量不要写内部样式或者在<script>标签里写js代码
(2)css文件中写url的时候注意路径问题,特别是在配置文件里设置了publicPath的情况下
(3)js文件中涉及到文件引入的,可以用require或define或import from,注意采用这种引入的前提是被引入文件已经进行了模块化的代码规范
(4)如果不想单独引入某个文件,可以在全局挂载插件中进行设置,区别于window挂载
(5)vue组件中涉及到图片路径(主要是src形式)报错的,可以在路径外层加上require(),这样测试环境下是没有问题的,但是生产环境下可能还会报错,关键在于弄清楚两种环境下图片的相对路径分别是相对于谁
(6)webpack加载css的时候,遇到font-family一定要去掉属性值的空格和双引号,不然解析出来的样式会出错

 

未完待续~

 

posted @ 2016-11-30 22:49  衣冠小禽兽  阅读(8209)  评论(2编辑  收藏  举报