用vue构建多页面应用的示例代码


Posted in Javascript onSeptember 20, 2017

最近一直在研究使用vue做出来一些东西,但都是SPA的单页面应用,但实际工作中,单页面并不一定符合业务需求,所以这篇我就来说说怎么开发多页面的Vue应用,以及在这个过程会遇到的问题。

这是我放在GitHub上的项目,里面有整个配置文件,可以参看一下:multiple-vue-page

准备工作

在本地用vue-cli新建一个项目,这个步骤vue的官网上有,我就不再说了。

这里有一个地方需要改一下,在执行npm install命令之前,在package.json里添加一个依赖,后面会用到。

用vue构建多页面应用的示例代码

修改webpack配置

这里展示一下我的项目目录

├── README.md
├── build
│  ├── build.js
│  ├── check-versions.js
│  ├── dev-client.js
│  ├── dev-server.js
│  ├── utils.js
│  ├── vue-loader.conf.js
│  ├── webpack.base.conf.js
│  ├── webpack.dev.conf.js
│  └── webpack.prod.conf.js
├── config
│  ├── dev.env.js
│  ├── index.js
│  └── prod.env.js
├── package.json
├── src
│  ├── assets
│  │  └── logo.png
│  ├── components
│  │  ├── Hello.vue
│  │  └── cell.vue
│  └── pages
│    ├── cell
│    │  ├── cell.html
│    │  ├── cell.js
│    │  └── cell.vue
│    └── index
│      ├── index.html
│      ├── index.js
│      ├── index.vue
│      └── router
│        └── index.js
└── static

在这一步里我们需要改动的文件都在build文件下,分别是:

  • utils.js
  • webpack.base.conf.js
  • webpack.dev.conf.js
  • webpack.prod.conf.js

我就按照顺序放出完整的文件内容,然后在做修改或添加的位置用注释符标注出来:

utils.js文件

// utils.js文件

var path = require('path')
var config = require('../config')
var ExtractTextPlugin = require('extract-text-webpack-plugin')

exports.assetsPath = function (_path) {
  var assetsSubDirectory = process.env.NODE_ENV === 'production' ?
    config.build.assetsSubDirectory :
    config.dev.assetsSubDirectory
  return path.posix.join(assetsSubDirectory, _path)
}

exports.cssLoaders = function (options) {
  options = options || {}

  var cssLoader = {
    loader: 'css-loader',
    options: {
      minimize: process.env.NODE_ENV === 'production',
      sourceMap: options.sourceMap
    }
  }

  // generate loader string to be used with extract text plugin
  function generateLoaders(loader, loaderOptions) {
    var loaders = [cssLoader]
    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }

    // Extract CSS when that option is specified
    // (which is the case during production build)
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }

  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
  return {
    css: generateLoaders(),
    postcss: generateLoaders(),
    less: generateLoaders('less'),
    sass: generateLoaders('sass', { indentedSyntax: true }),
    scss: generateLoaders('sass'),
    stylus: generateLoaders('stylus'),
    styl: generateLoaders('stylus')
  }
}

// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
  var output = []
  var loaders = exports.cssLoaders(options)
  for (var extension in loaders) {
    var loader = loaders[extension]
    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }
  return output
}

/* 这里是添加的部分 ---------------------------- 开始 */

// glob是webpack安装时依赖的一个第三方模块,还模块允许你使用 *等符号, 例如lib/*.js就是获取lib文件夹下的所有js后缀名的文件
var glob = require('glob')
// 页面模板
var HtmlWebpackPlugin = require('html-webpack-plugin')
// 取得相应的页面路径,因为之前的配置,所以是src文件夹下的pages文件夹
var PAGE_PATH = path.resolve(__dirname, '../src/pages')
// 用于做相应的merge处理
var merge = require('webpack-merge')


//多入口配置
// 通过glob模块读取pages文件夹下的所有对应文件夹下的js后缀文件,如果该文件存在
// 那么就作为入口处理
exports.entries = function () {
  var entryFiles = glob.sync(PAGE_PATH + '/*/*.js')
  var map = {}
  entryFiles.forEach((filePath) => {
    var filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'))
    map[filename] = filePath
  })
  return map
}

//多页面输出配置
// 与上面的多页面入口配置相同,读取pages文件夹下的对应的html后缀文件,然后放入数组中
exports.htmlPlugin = function () {
  let entryHtml = glob.sync(PAGE_PATH + '/*/*.html')
  let arr = []
  entryHtml.forEach((filePath) => {
    let filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'))
    let conf = {
      // 模板来源
      template: filePath,
      // 文件名称
      filename: filename + '.html',
      // 页面模板需要加对应的js脚本,如果不加这行则每个页面都会引入所有的js脚本
      chunks: ['manifest', 'vendor', filename],
      inject: true
    }
    if (process.env.NODE_ENV === 'production') {
      conf = merge(conf, {
        minify: {
          removeComments: true,
          collapseWhitespace: true,
          removeAttributeQuotes: true
        },
        chunksSortMode: 'dependency'
      })
    }
    arr.push(new HtmlWebpackPlugin(conf))
  })
  return arr
}
/* 这里是添加的部分 ---------------------------- 结束 */

webpack.base.conf.js 文件

// webpack.base.conf.js 文件

var path = require('path')
var utils = require('./utils')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf')

function resolve(dir) {
 return path.join(__dirname, '..', dir)
}

module.exports = {
 /* 修改部分 ---------------- 开始 */
 entry: utils.entries(),
 /* 修改部分 ---------------- 结束 */
 output: {
  path: config.build.assetsRoot,
  filename: '[name].js',
  publicPath: process.env.NODE_ENV === 'production' ?
   config.build.assetsPublicPath :
   config.dev.assetsPublicPath
 },
 resolve: {
  extensions: ['.js', '.vue', '.json'],
  alias: {
   'vue$': 'vue/dist/vue.esm.js',
   '@': resolve('src'),
   'pages': resolve('src/pages'),
   'components': resolve('src/components')
  }
 },
 module: {
  rules: [{
   test: /\.vue$/,
   loader: 'vue-loader',
   options: vueLoaderConfig
  },
  {
   test: /\.js$/,
   loader: 'babel-loader',
   include: [resolve('src'), resolve('test')]
  },
  {
   test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
   loader: 'url-loader',
   options: {
    limit: 10000,
    name: utils.assetsPath('img/[name].[hash:7].[ext]')
   }
  },
  {
   test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
   loader: 'url-loader',
   options: {
    limit: 10000,
    name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
   }
  }
  ]
 }
}

webpack.dev.conf.js 文件

var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')

// 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: {
  rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
 },
 // cheap-module-eval-source-map is faster for development
 devtool: '#cheap-module-eval-source-map',
 plugins: [
  new webpack.DefinePlugin({
   'process.env': config.dev.env
  }),
  // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
  new webpack.HotModuleReplacementPlugin(),
  new webpack.NoEmitOnErrorsPlugin(),
  // https://github.com/ampedandwired/html-webpack-plugin
  /* 注释这个区域的文件 ------------- 开始 */
  // new HtmlWebpackPlugin({
  //  filename: 'index.html',
  //  template: 'index.html',
  //  inject: true
  // }),
  /* 注释这个区域的文件 ------------- 结束 */
  new FriendlyErrorsPlugin()

  /* 添加 .concat(utils.htmlPlugin()) ------------------ */
 ].concat(utils.htmlPlugin())
})

webpack.prod.conf.js 文件

var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var CopyWebpackPlugin = require('copy-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')

var env = config.build.env

var webpackConfig = merge(baseWebpackConfig, {
 module: {
  rules: 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')
 },
 plugins: [
  // http://vuejs.github.io/vue-loader/en/workflow/production.html
  new webpack.DefinePlugin({
   'process.env': env
  }),
  new webpack.optimize.UglifyJsPlugin({
   compress: {
    warnings: false
   },
   sourceMap: true
  }),
  // extract css into its own file
  new ExtractTextPlugin({
   filename: utils.assetsPath('css/[name].[contenthash].css')
  }),
  // Compress extracted CSS. We are using this plugin so that possible
  // duplicated CSS from different components can be deduped.
  new OptimizeCSSPlugin({
   cssProcessorOptions: {
    safe: true
   }
  }),
  // 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

  /* 注释这个区域的内容 ---------------------- 开始 */
  // new HtmlWebpackPlugin({
  //  filename: config.build.index,
  //  template: 'index.html',
  //  inject: true,
  //  minify: {
  //   removeComments: true,
  //   collapseWhitespace: true,
  //   removeAttributeQuotes: true
  //   // more options:
  //   // https://github.com/kangax/html-minifier#options-quick-reference
  //  },
  //  // necessary to consistently work with multiple chunks via CommonsChunkPlugin
  //  chunksSortMode: 'dependency'
  // }),
  /* 注释这个区域的内容 ---------------------- 结束 */

  // 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']
  }),
  // copy custom static assets
  new CopyWebpackPlugin([{
   from: path.resolve(__dirname, '../static'),
   to: config.build.assetsSubDirectory,
   ignore: ['.*']
  }])
  /* 该位置添加 .concat(utils.htmlPlugin()) ------------------- */
 ].concat(utils.htmlPlugin())
})

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
  })
 )
}

if (config.build.bundleAnalyzerReport) {
 var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
 webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

至此,webpack的配置就结束了。

但是还没完啦,下面继续。

文件结构

├── src
│  ├── assets
│  │  └── logo.png
│  ├── components
│  │  ├── Hello.vue
│  │  └── cell.vue
│  └── pages
│    ├── cell
│    │  ├── cell.html
│    │  ├── cell.js
│    │  └── cell.vue
│    └── index
│      ├── index.html
│      ├── index.js
│      ├── index.vue
│      └── router
│        └── index.js

src就是我所使用的工程文件了,assets,components,pages分别是静态资源文件、组件文件、页面文件。

前两个就不多说,主要是页面文件里,我目前是按照项目的模块分的文件夹,你也可以按照你自己的需求调整。然后在每个模块里又有三个内容:vue文件,js文件和html文件。这三个文件的作用就相当于做spa单页面应用时,根目录的index.html页面模板,src文件下的main.js和app.vue的功能。

原先,入口文件只有一个main.js,但现在由于是多页面,因此入口页面多了,我目前就是两个:index和cell,之后如果打包,就会在dist文件下生成两个HTML文件:index.html和cell.html(可以参考一下单页面应用时,打包只会生成一个index.html,区别在这里)。

cell文件下的三个文件,就是一般模式的配置,参考index的就可以,但并不完全相同。

特别注意的地方

cell.js

在这个文件里,按照写法,应该是这样的吧:

import Vue from 'Vue'
import cell from './cell.vue'

new Vue({
  el:'#app',// 这里参考cell.html和cell.vue的根节点id,保持三者一致
  teleplate:'<cell/>',
  components:{ cell }
})

这个配置在运行时(npm run dev)会报错

[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
(found in <Root>)

网上的解释是这样的:

运行时构建不包含模板编译器,因此不支持 template 选项,只能用 render 选项,但即使使用运行时构建,在单文件组件中也依然可以写模板,因为单文件组件的模板会在构建时预编译为 render 函数。运行时构建比独立构建要轻量30%,只有 17.14 Kb min+gzip大小。

上面一段是官方api中的解释。就是说,如果我们想使用template,我们不能直接在客户端使用npm install之后的vue。
也给出了相应的修改方案:

resolve: { alias: { 'vue': 'vue/dist/vue.js' } }

这里是修改package.json的resolve下的vue的配置,很多人反应这样修改之后就好了,但是我按照这个方法修改之后依然报错。然后我就想到上面提到的render函数,因此我的修改是针对cell.js文件的。

import Vue from 'Vue'
import cell from './cell.vue'

/* eslint-disable no-new */
new Vue({
 el: '#app',
 render: h => h(cell)
})

这里面我用render函数取代了组件的写法,在运行就没问题了。

页面跳转

既然是多页面,肯定涉及页面之间的互相跳转,就按照我这个项目举例,从index.html文件点击a标签跳转到cell.html。

我最开始写的是:

<!-- index.html -->
<a href='../cell/cell.html'></a>

但这样写,不论是在开发环境还是最后测试,都会报404,找不到这个页面。

改成这样既可:

<!-- index.html -->
<a href='cell.html'></a>

这样他就会自己找cell.html这个文件。

打包后的资源路径

执行npm run build之后,打开相应的html文件你是看不到任何东西的,查看原因是找不到相应的js文件和css文件。

这时候的文件结构是这样的:

├── dist
│  ├── js
│  ├── css
│  ├── index.html
│  └── cell.html

查看index.html文件之后会发现资源的引用路径是:

/dist/js.........

这样,如果你的dist文件不是在根目录下的,就根本找不到资源。

方法当然也有啦,如果你不嫌麻烦,就一个文件一个文件的修改路径咯,或者像我一样偷懒,修改config下的index.js文件。具体的做法是:

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'],
  // Run the build command with an extra argument to
  // View the bundle analyzer report after build finishes:
  // `npm run build --report`
  // Set to `true` or `false` to always turn it on or off
  bundleAnalyzerReport: process.env.npm_config_report
 },

将这里面的

assetsPublicPath: '/',

改成

assetsPublicPath: './',

酱紫,配置文件资源的时候找到的就是相对路径下的资源了,在重新npm run build看看吧。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery 常见开发使用技巧总结
Dec 26 Javascript
基于jquery跨浏览器显示的file上传控件
Oct 24 Javascript
jQuery+ajax实现实用的点赞插件代码
Jul 06 Javascript
JS中this上下文对象使用方式
Oct 09 Javascript
微信小程序 wxapp导航 navigator详解
Oct 31 Javascript
JavaScript中创建对象的7种模式详解
Feb 21 Javascript
分享一个精简的vue.js 图片lazyload插件实例
Mar 13 Javascript
vue init失败简单解决方法(终极版)
Dec 22 Javascript
element ui 对话框el-dialog关闭事件详解
Feb 26 Javascript
vue下拉菜单组件(含搜索)的实现代码
Nov 25 Javascript
使用webpack搭建pixi.js开发环境
Feb 12 Javascript
js仿淘宝放大镜效果
Dec 28 Javascript
微信小程序request请求后台接口php的实例详解
Sep 20 #Javascript
微信小程序 input表单与redio及下拉列表的使用实例
Sep 20 #Javascript
JavaScript实现图片本地预览功能【不用上传至服务器】
Sep 20 #Javascript
微信小程序picker组件下拉框选择input输入框的实例
Sep 20 #Javascript
jQueryMobile之窗体长内容的缺陷与解决方法实例分析
Sep 20 #jQuery
微信小程序授权获取用户详细信息openid的实例详解
Sep 20 #Javascript
jQuery实现IE输入框完成placeholder标签功能的方法
Sep 20 #jQuery
You might like
destoon整合ucenter后注册页面不跳转的解决方法
2014/06/21 PHP
ThinkPHP基本的增删查改操作实例教程
2014/08/22 PHP
wamp服务器访问php非常缓慢的解决过程
2015/07/01 PHP
yii框架无限极分类的实现方法
2017/04/08 PHP
thinkphp5.1 文件引入路径问题及注意事项
2018/06/13 PHP
阿里对象存储OSS在laravel框架中的使用方法
2019/10/13 PHP
js 颜色选择器(兼容firefox)
2009/03/05 Javascript
jquery nth-child()选择器的简单应用
2010/07/10 Javascript
JQury slideToggle闪烁问题及解决办法
2011/07/05 Javascript
jquery ajax方式直接提交整个表单核心代码
2013/08/15 Javascript
JsRender for index循环索引用法详解
2014/10/31 Javascript
JavaScript中的console.assert()函数介绍
2014/12/29 Javascript
jQuery向后台传入json格式数据的方法
2015/02/13 Javascript
jQuery获取访问者IP地址的方法(基于新浪API与QQ查询接口)
2016/05/25 Javascript
JavaScript 字符串常用操作小结(非常实用)
2016/11/30 Javascript
javascript实现文字无缝滚动
2016/12/27 Javascript
在vue.js中抽出公共代码的方法示例
2017/06/08 Javascript
javascript 中select框触发事件过程的分析
2017/08/01 Javascript
vue vuex vue-rouert后台项目——权限路由(适合初学)
2017/12/29 Javascript
jQuery基于闭包实现的显示与隐藏div功能示例
2018/06/09 jQuery
vue中设置height:100%无效的问题及解决方法
2018/07/27 Javascript
js实现验证码干扰(动态)
2021/02/23 Javascript
用不到50行的Python代码构建最小的区块链
2017/11/16 Python
Python多进程入门、分布式进程数据共享实例详解
2019/06/03 Python
Python3基于plotly模块保存图片表格
2020/08/03 Python
Canvas高级路径操作之拖拽对象的实现
2019/08/05 HTML / CSS
GOOD AMERICAN官网:为曲线性感而设计
2017/12/28 全球购物
德国便宜的宠物店:Brekz.de
2020/10/23 全球购物
AJax面试题
2014/11/25 面试题
执行力心得体会
2013/12/31 职场文书
一年级数学教学反思
2014/02/01 职场文书
小学生感恩演讲稿
2014/04/25 职场文书
飞机制造技术专业求职信
2014/07/27 职场文书
mysql 带多个条件的查询方式
2021/06/05 MySQL
spring boot项目application.properties文件存放及使用介绍
2021/06/30 Java/Android
Android 界面一键变灰 深色主题工具类
2022/04/28 Java/Android