解锁 Vue多页面应用

大部分使用Vue是构建单页面应用,但有时候我们也需要多页面应用,比如手机端的H5页面,可能这块需要一个H5页,另一块需要一个H5活动页,彼此相互独立,根本就没有什么关联,这时候还使用单页面应用增加了页面加载的速度,而且打包了一大堆和本页面无关的代码,增加了页面响应时间。

准备工作:
使用vue-cli构建工具,添加项目。
最近vue-cli发布了最新版本v3.0.0,如果使用最新版本的脚手架,就不会暴露出webpack的一些配置文件,没办法自定义了,所以本次配置多页面构建还是使用的Vue CLI 2

npm install -g vue-cli

vue init webpack vue-mutile-page

使用此模板创建的vue项目,集成的是webpack3,现在webpack已经升级到4了,暂时先按照模板来使用webpack3吧,先把架子搭好,后期想升级到最新也是可以的。

单页面应用打包之后dist目录

直接使用模板创建的项目是单页面应用的,配置文件都是写好的,所以我们可以直接执行命令打包

npm run build

打包完成之后会生成dist目录,只生成一个入口文件index.html,然后static中有打包好的js和css文件.

├── dist
|    ├── index.html
|    └── static
|       ├── css
|       └── js

多页面应用

上面是常见的单页面应用打包之后的目录,那多页面打包之后应该是什么样的呢?

多页面应用就意味着有多个页面,单页面打包之后只生成一个html文件,这个文件就是项目的入口文件,多页面也就是说每个页面都应该有一个自己的html文件,页面和页面之间是相互独立的,那在打包之后dist目录应该生成每个页面的html文件。比如我有page1和page2,dist目录中应该生成下面的结构,各自的页面名称对应各自的html文件。那要怎么做到呢,请看下文分解。

├── dist
|    ├── page1.html
|    ├── page1.html
|    └── static
|       ├── css
|       │   
|       └── js
|       

改写项目目录

想要搭建多页面的应用,首先要改写刚刚生成的项目目录结构。

下面是我修改之后的目录结构

.
├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .postcssrc.js
├── README.md
├── build
│   ├── build.js
│   ├── check-versions.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
│   └── test.env.js
├── package.json
├── src
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   └── pages
│       ├── page1
│       │   ├── App.vue
│       │   ├── index.ejs
│       │   └── index.js
│       └── page2
│           ├── App.vue
│           ├── index.ejs
│           └── index.js
└── static

修改的点

  1. 在src文件下创建pages目录,这是我们创建多页面放置的地方
  2. pages目录下是根据页面名称命名的文件夹,文件夹下包含三个文件,后缀为:.vue .ejs .js这三个文件,后缀为.vue 和 .js 的文件分别对应项目创建时的App.vue 和main.js文件,现在都放到页面文件中,.ejs文件对应的是根目录下的index.html文件,也就是项目的入口文件。
  3. 把src下的App.vue 和main.js删除,根目录下的index.html也删除,因为这些都已经移到页面中了。

项目目录整理好之后,那接下来我们就可以写配置文件了,配置文件的修改都在build目录下。更改的目录主要有下面这几个

├── build
│   ├── utils.js
│   ├── webpack.base.conf.js
│   ├── webpack.dev.conf.js
│   └── webpack.prod.conf.js

修改utils.js文件

在utils文件中增加两个函数,一个是获取多页面路径作为entry文件的入口,另一个是生成页面多页面plugin的配置,废话不多说,上代码。

首先需要安装一个node模块glob,glob可以读取相应后缀名的文件,便于检索出需要的文件。

npm install --save-dev glob

在utils.js中添加如下代码

const glob = require('glob')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const merge = require('webpack-merge')

const PAGE_PATH = path.resolve(__dirname, '../src/pages')

// 多页面入口文件配置
exports.entries = function () {
//  读取pages下的 页面名称文件下的后缀为js的文件
  var entryFiles = glob.sync(PAGE_PATH + '/*/*.js')
  var map = {}
  entryFiles.forEach((filePath) => {
    // 因为后缀为js的文件名在index,而我们想获取的是
    var pathArry = filePath.split('/')
    var filename = pathArry[pathArry.length - 2]
    map[filename] = filePath
  })
  // 整理成文件名为Key,路径为value的Object对象
  return map
}

// 多页面输出配置
exports.htmlPlugin = function () {
  let entryHtml = glob.sync(PAGE_PATH + '/*/*.ejs')
  let arr = []
  entryHtml.forEach((filePath) => {
    let pathArry = filePath.split('/')
    // 获取页面名
    let filename = pathArry[pathArry.length - 2]

    let conf = {
      template: filePath,
      filename: filename + '.html',
      chunks: ['manifest', 'vendor', filename],
      inject: true
    }

    if (process.env.NODE_ENV === 'production') {
      conf = merge(conf, {
        minify: {
          removeComments: true,
          collapseWhitespace: true,
          removeAttributeQuotos: true
        },
        chunksSortMode: 'dependency'
      })
    }
    arr.push(new HtmlWebpackPlugin(conf))
  })
  return arr
}

修改webpack.base.conf.js

代码如下,修改的部分只有入口文件的配置

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

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

const createLintingRule = () => ({
  test: /\.(js|vue)$/,
  loader: 'eslint-loader',
  enforce: 'pre',
  include: [resolve('src'), resolve('test')],
  options: {
    formatter: require('eslint-friendly-formatter'),
    emitWarning: !config.dev.showEslintErrorsInOverlay
  }
})

module.exports = {
  context: path.resolve(__dirname, '../'),
  // 修改的部分=====开始
  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'),
    }
  },
  module: {
    rules: [
      ...(config.dev.useEslint ? [createLintingRule()] : []),
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  node: {
    // prevent webpack from injecting useless setImmediate polyfill because Vue
    // source contains it (although only uses it if it's native).
    setImmediate: false,
    // prevent webpack from injecting mocks to Node native modules
    // that does not make sense for the client
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }
}

webpack.dev.conf.js文件

完整代码比较多,只贴出了关键代码

  plugins: [
    new webpack.DefinePlugin({
      'process.env': require('../config/dev.env')
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
    new webpack.NoEmitOnErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin

    //-------- 注掉 配置中的 HtmlWebpackPlugin 这个插件---------------

   // new HtmlWebpackPlugin({
    //  filename: 'index.html',
    //  template: 'index.html',
    //  inject: true
   // }),

   // ------------------------------------------------------------

    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.dev.assetsSubDirectory,
        ignore: ['.*']
      }
    ])

    // 添加----------utils中我们写的另一个函数utils.htmlPlugin-----------
  ].concat(utils.htmlPlugin())
  // ---------------------------------------

webpack.prod.conf.js文件

生产环境的配置和开发环境一样,先注掉配置文件中的webpack插件HtmlWebpackPlugin的那段代码,然后在将utils中我们之前写的htmlplugin函数,生成的数组添加到plugin上。操作和上面webpack.dev.conf.js文件相同。


好了一切配置完成,现在就可以实验一下了,执行

npm run build

如果没什么问题的话就可以看到dist目录中生成,在文章开始我们设想的打包之后的页面结构。

GitHub项目地址:Vue-multiple-page

参考资料

官网Entry Points介绍
https://webpack.js.org/concepts/entry-points/#src/components/Sidebar/Sidebar.jsx

HtmlWebpackPlugin
https://github.com/jantimon/html-webpack-plugin

Vue多页面应用 blog
https://segmentfault.com/a/1190000011265006

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 1 Webpack 1.1 概念简介 1.1.1 WebPack是什么 1、一个打包工具 2、一个模块加载工具 3...
    Kevin_Junbaozi阅读 6,629评论 0 16
  • 写在开头 先说说为什么要写这篇文章, 最初的原因是组里的小朋友们看了webpack文档后, 表情都是这样的: (摘...
    Lefter阅读 5,269评论 4 31
  • 相关概念 混合开发和前后端分离 混合开发(服务器端渲染) 前后端分离后端提供接口,前端开发界面效果(专注于用户的交...
    他爱在黑暗中漫游阅读 2,759评论 4 45
  • 协作模式复盘,上周日尝试了新的协作模式,下面总结下整个过程,包括其中发现的问题及经验。 首先,介绍下这次协作模式尝...
    正经小草阅读 404评论 0 0
  • 佛经曰:人生有七苦,生、老、病、死、怨憎会、爱别离、求不得。求之而不得这一压轴的痛苦,现代人十之有八九会经常...
    遇见木子阅读 1,072评论 1 3