Vue从入门到精通(7)--第四阶段(下):进阶

第四阶段(下)

  1. vue-router、vue-resource、vuex、mint-ui
  2. 多页面、跨域问题

vue-router、vue-resource、vuex、mint-ui

    vue-router、vue-resource

    示例教程在《官方脚手架vue-cli》中已经提到,这里提供vue-router的官方网站(相信你在学习的过程中已经发现了)。

    vuex

    作为较小的项目来说,vue提供的基于消息的bus机制足够我们处理在不同组件之间的数据交换。如果大型项目,则需要使用vuex了。这个也不需要看第三方的教程,官方文档 给的很详细。

    Mint UI
    Mint UI 是一套基于Vue2.0的UI库,官方文档做的很不错,跟着官网的教程很容易入门。如果仍然觉得有点难度,则先看之知乎上的一篇《Mint UI —— 基于 Vue.js 的移动端组件库》


多页面、跨域问题

多页面

    vue-cli是个单页面应用(SPA),即只有一个html的应用。无论你使用了路由还是组件的动态加载,都依然只是一个HTML页面。大型的网站一般都是由多个html页面组成的(MPA),最简单的来说,注册、登录和系统三个页面就应该是三个html。那么我们怎么配置才能做到多页面应用呢?有如下两个方法:

<1> 第一种方法

    参看 这里的教程(在B站看视频教程,想不到吧):
    
    教程里是一个台湾的讲师,总共出了4个视频。个人认为其他三个教程没什么参考价值,甚至会让你听得云里雾里。只有这一篇讲解配置多页面的可以照猫画虎的弄下来,顺便研究下他的配置项。注意,是研究,而不是听他讲,他讲到了一两个关键点,但不全面。
    
    视频较为拖沓,如果你不想看,我把需要的配置项都弄下来了,如下:

    1、webpack.base.conf.js

     Vue从入门到精通(7)--第四阶段(下):进阶_第1张图片

    2、项目的src路径下:

    Vue从入门到精通(7)--第四阶段(下):进阶_第2张图片

    3、webpage.dev.conf.js

    这里写图片描述

    改为:

    Vue从入门到精通(7)--第四阶段(下):进阶_第3张图片

    其中,chunk数组中的字符串,对应webpack.base.conf.js中entry下的键值。数组中的字符串的键值对应的打包的js,最终都会在相应的html中引用。

    4、项目根路径下新建两个html,之后,h0201.js相当于main.js,h0201.vue相当于App.vue,按照对应的方式写代码即可。
    
    5、下面针对npm run build发布配置
    config/index.js文件中build项中添加:

        Vue从入门到精通(7)--第四阶段(下):进阶_第4张图片

    在config/index.js文件中plugins项中添加:

    Vue从入门到精通(7)--第四阶段(下):进阶_第5张图片
    
    Vue从入门到精通(7)--第四阶段(下):进阶_第6张图片

<2> 第二种方法
    第一种方法有一个弊端,就是你每增加一个html页面,都需要重新配置一系列的项,这很麻烦,也不能体现webpack的先进性。于是在github上找到了一个nodejs大神搭建的多页面脚手架,基于vue-cli单页应用改造,自动配置每个页面的主文件等内容。唯一的限制是我们需要按照他提供的项目目录结构来开发。我们需要做的就是下载下来然后通过npm安装依赖的js包,之后就可以开发了。
    
    Github页面

使用方法

# install dependencies
npm install

# serve with hot reload at localhost:8080/module/index.html
npm run dev

# build for production with minification
npm run build

目录结构

vue-cli-multipage
  |---build
  |---config
  |---src
    |---assets
      |---img 图片文件
      |---css 样式文件
      |---font 字体文件      
    |---components  组件
      |---Button.vue 按钮组件
      |---Hello.vue
    |---module
      |---index  首页模块
        |---index.html
        |---index.js
        |---App.vue
      |---detail  详情页模块
        |---detail.html
        |---detail.js
        |---App.vue

    从目录结构上,各种组件、页面模块、资源等都按类新建了文件夹,方便我们储存文件。
    其实我们所有的文件,最主要都是放在module文件夹里,以文件夹名为html的名称。 例如:

|---index  首页模块
  |---index.html
  |---index.js
  |---App.vue

    此时我们访问的链接是: http://localhost:8080/module/index.html

    这里一定要注意,在module里下级文件夹里需要将html,js,vue template都统一放在当前文件夹里,当然你也可以继续放其他的资源,例如css、图片、组件等,webpack会打包到当前页面里。

    如果项目不需要这个页面了,可以把这个文件夹直接删除掉,干净利落,干活也开心。

    像以前传统的开发项目,所有的图片都习惯放在images里,当项目有改动时,有些图片等资源用不上了,但还占着坑位,导致项目越来越大,虽然现在的硬件容量大到惊人,但我们应该还是要养到一个良好的习惯。

    构建代码说明

    我们使用vue-cli的手脚架,构建代码配置文件放在目录build下,vue多页面修改了这三个JS文件:index.js、webpack.base.conf.js、webpack.dev.conf.js、webpack.prod.conf.js

    项目与单页面vue-cli脚手架对比,代码修改的地方我也该找出来了,感兴趣的话可以自己与单页面的相关代码对比下,看看他的实现原理:

[index.js]

// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')

module.exports = {
  build: {
    env: require('./prod.env'),
    index: path.resolve(__dirname, '../dist/index.html'),
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '../',
    productionSourceMap: true,
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css']
  },
  dev: {
    env: require('./dev.env'),
    port: 8080,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}

[webpack.base.conf.js]

var path = require('path')
var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
var glob = require('glob');
var entries = getEntry('./src/module/**/*.js'); // 获得入口js文件

var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
// various preprocessor loaders added to vue-loader at the end of this file
var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
var useCssSourceMap = cssSourceMapDev || cssSourceMapProd

module.exports = {
  entry: entries,
  output: {
    path: config.build.assetsRoot,
    publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
    filename: '[name].js'
  },
  resolve: {
    extensions: ['', '.js', '.vue'],
    fallback: [path.join(__dirname, '../node_modules')],
    alias: {
      'vue$': 'vue/dist/vue',
      'src': path.resolve(__dirname, '../src'),
      'common': path.resolve(__dirname, '../src/common'),
      'components': path.resolve(__dirname, '../src/components')
    }
  },
  resolveLoader: {
    fallback: [path.join(__dirname, '../node_modules')]
  },
  module: {
    loaders: [
      {
        test: /\.vue$/,
        loader: 'vue'
      },
      {
        test: /\.js$/,
        loader: 'babel',
        include: projectRoot,
        exclude: /node_modules/
      },
      {
        test: /\.json$/,
        loader: 'json'
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url',
        query: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url',
        query: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  vue: {
    loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }),
    postcss: [
      require('autoprefixer')({
        browsers: ['last 2 versions']
      })
    ]
  }
}

function getEntry(globPath) {
  var entries = {},
    basename, tmp, pathname;

  glob.sync(globPath).forEach(function (entry) {
    basename = path.basename(entry, path.extname(entry));
    tmp = entry.split('/').splice(-3);
    pathname = tmp.splice(0, 1) + '/' + basename; // 正确输出js和html的路径
    entries[pathname] = entry;
  });

  return entries;
}

[webpack.base.dev.js]

var path = require('path');
var config = require('../config')
var webpack = require('webpack')
var merge = require('webpack-merge')
var utils = require('./utils')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var glob = require('glob')

// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
  baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})

module.exports = merge(baseWebpackConfig, {
  module: {
    loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
  },
  // eval-source-map is faster for development
  devtool: '#eval-source-map',
  plugins: [
    new webpack.DefinePlugin({
      'process.env': config.dev.env
    }),
    // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ]
})

function getEntry(globPath) {
  var entries = {},
    basename, tmp, pathname;

  glob.sync(globPath).forEach(function (entry) {

    basename = path.basename(entry, path.extname(entry));
    tmp = entry.split('/').splice(-3);
    pathname = tmp.splice(0, 1) + '/' + basename; // 正确输出js和html的路径

    entries[pathname] = entry;
  });

  return entries;
}

var pages = getEntry('./src/module/**/*.html');

for (var pathname in pages) {
  // 配置生成的html文件,定义路径等
  var conf = {
    filename: pathname + '.html',
    template: pages[pathname],   // 模板路径
    inject: true,              // js插入位置
    minify: {
      //removeComments: true,
      //collapseWhitespace: true,
      //removeAttributeQuotes: true
    },
    // necessary to consistently work with multiple chunks via CommonsChunkPlugin
    chunksSortMode: 'dependency'

  };

  if (pathname in module.exports.entry) {
    conf.chunks = ['manifest', 'vendor', pathname];
    conf.hash = true;
  }

  module.exports.plugins.push(new HtmlWebpackPlugin(conf));
}

[webpack.base.prod.js]

var path = require('path')
var config = require('../config')
var utils = require('./utils')
var webpack = require('webpack')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var CleanPlugin = require('clean-webpack-plugin')//webpack插件,用于清除目录文件
var glob = require('glob');
var env = config.build.env

var webpackConfig = merge(baseWebpackConfig, {
  module: {
    loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true })
  },
  devtool: config.build.productionSourceMap ? '#source-map' : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  vue: {
    loaders: utils.cssLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true
    })
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),
    new CleanPlugin(['../dist']), //清空生成目录
    new webpack.optimize.OccurenceOrderPlugin(),
    // extract css into its own file
    new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
    // generate dist index.html with correct asset hash for caching.
    // you can customize output by editing /index.html
    // see https://github.com/ampedandwired/html-webpack-plugin
    // split vendor js into its own file
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: function (module, count) {
        // any required modules inside node_modules are extracted to vendor
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
    // extract webpack runtime and module manifest to its own file in order to
    // prevent vendor hash from being updated whenever app bundle is updated
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      chunks: ['vendor']
    })
  ]
})

if (config.build.productionGzip) {
  var CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

module.exports = webpackConfig

function getEntry(globPath) {
  var entries = {},
    basename, tmp, pathname;

  glob.sync(globPath).forEach(function (entry) {
    basename = path.basename(entry, path.extname(entry));
    tmp = entry.split('/').splice(-3);
    pathname = tmp.splice(0, 1) + '/' + basename; // 正确输出js和html的路径
    console.log("路径key", pathname);
    entries[pathname] = entry;
  });
  return entries;
}

var pages = getEntry('./src/module/**/*.html');

for (var pathname in pages) {
  // 配置生成的html文件,定义路径等
  console.log("pathname", pathname);
  var conf = {
    filename: pathname + '.html',
    template: pages[pathname],   // 模板路径
    inject: true,              // js插入位置
    minify: {
      //removeComments: true,
      //collapseWhitespace: true,
      //removeAttributeQuotes: true
    },
    // necessary to consistently work with multiple chunks via CommonsChunkPlugin
    chunksSortMode: 'dependency'
  };

  if (pathname in module.exports.entry) {
    console.log("1", pathname);
    conf.chunks = ['manifest', 'vendor', pathname];
    conf.hash = true;
  }

  module.exports.plugins.push(new HtmlWebpackPlugin(conf));
}

跨域问题

    vue-cli是典型的前后端隔离开发脚手架,同时vue-cli自己启动了一个express服务来充当服务器,此时我们调用其他服务上的api时,浏览器会报跨域冲突。

    解决方法

    vue-cli的config/index.js文件里有一个参数叫proxyTable。在vuejs-templates,也就是vue-cli的使用的模板插件里,有关于API proxy的说明,使用的就是这个参数。

    这个参数主要是一个地址映射表,你可以通过设置将复杂的url简化,例如我们要请求的地址是api.xxxxxxxx.com/list/1,可以按照如下设置:

proxyTable: {
  '/list': {
    target: 'http://api.xxxxxxxx.com',
    pathRewrite: {
      '^/list': '/list'
    }
  }
}

    这样我们在写url的时候,只用写成/list/1就可以代表api.xxxxxxxx.com/list/1.

    那么又是如何解决跨域问题的呢?其实在上面的’list’的参数里有一个changeOrigin参数,接收一个布尔值,如果设置为true,那么本地会虚拟一个服务端接收你的请求并代你发送该请求,这样就不会有跨域问题了,当然这只适用于开发环境。增加的代码如下所示:

proxyTable: {
  '/list': {
    target: 'http://api.xxxxxxxx.com',
    changeOrigin: true,
    pathRewrite: {
      '^/list': '/list'
    }
  }
}

    vue-cli的这个设置来自于其使用的插件http-proxy-middleware(github),深入了解的话可以看该插件配置说明,似乎还支持websocket,很强大的插件。



                                                 文/almon123(简书作者) 原文链接

注意:
    
1、如果proxyTable中有多个具有相同前缀的键值,则将比较长的那个放在前面。因为在解析url时是按照proxyTable中的定义顺序来解析的。如下截图中,如果将proxyServerSDMAP写在proxyServerSDMAP8081前面,则http://proxyServerSDMAP8081将被解析成:http://www.sdmap.gov.cn/8081
    Vue从入门到精通(7)--第四阶段(下):进阶_第7张图片
    
2、跨域调用服务器302重定向后,重定向的location不是nodejs的本地服务地址怎么办?

    解决办法
    重定向后的location地址会把nodejs的本地服务地址转换成配置中的target地址,在onPorxyRes事件中再替换回本地服务地址就行,如下:
    Vue从入门到精通(7)--第四阶段(下):进阶_第8张图片

你可能感兴趣的:(vue)