vue实战之打包多页面

3,979 阅读8分钟

前言:

由于公司业务需要,网站需要被蜘蛛爬虫收录,要做seo,所以需要访问地址后需要添加.html后缀.

在大部分实际场景中,我们都可以构建单页应用来进行项目的开发和迭代,然而对于项目复杂度过高或者页面模块之间差异化较大的项目,我们可以选择构建多页应用来实现。

一,什么是vue的多页面?

我们可以把多页应用理解为由多个单页构成的应用,什么有啥多个单页呢?其实你可以把一个单页看成是一个 html 文件,那么多个单页便是多个 html 文件,多页应用便是由多个 html 组成的应用,如下图所示:

image.png

多页应用拥有多个 html,那么同样其应该拥有多个独立的入口文件、组件、路由、vuex 等。简单一点来说多页应用的每个单页都可以拥有单页应用 src 目录下的文件及功能,我们来看一下一个基础多页应用的目录结构:

├── node_modules               # 项目依赖包目录
├── build                      # 项目 webpack 功能目录
├── config                     # 项目配置项文件夹
├── src                        # 前端资源目录
│   ├── images                 # 图片目录
│   ├── components             # 公共组件目录
│   ├── views                 # 页面目录
│   │   ├── view1              # page1 目录
│   │   │   ├── components     # page1 组件目录
│   │   │   ├── router         # page1 路由目录
│   │   │   ├── views          # page1 页面目录
│   │   │   ├── view1.html     # page1 html 模板
│   │   │   ├── view1.vue      # page1 vue 配置文件
│   │   │   └── view1.js       # page1 入口文件
│   │   ├── view2              # page2 目录
│   │   └── index              # index 目录
│   ├── common                 # 公共方法目录
│   └── store                  # 状态管理 store 目录
├── .gitignore                 # git 忽略文件
├── .env                       # 全局环境配置文件
├── .env.dev                   # 开发环境配置文件
├── .postcssrc.js              # postcss 配置文件
├── babel.config.js            # babel 配置文件
├── package.json               # 包管理文件
├── vue.config.js              # CLI 配置文件

根据上方目录结构我们可以看出其实 views 下的一个目录就是一个单页包含的功能,这里我们包含了 3 个目录就构成了多页应用。

除了目录结构的不同外,其实区别单页应用,多页应用在很多配置上都需要进行修改,比如单入口变为多入口、单模板变为多模板等,那我们该这么操作修改呢?

二, '换斗移星',改变但入口为多入口

在单页应用中,我们的入口文件只有一个,cli 默认配置的是 main.js,但是到了多页应用,我们的入口文件便包含了 view1.js、view1.js、index.js等,数量取决于 view1 文件夹下目录的个数,这时候为了项目的可拓展性,我们需要自动计算入口文件的数量并解析路径配置到 webpack 中的 entry 属性上

module.exports = {
    ...
    
    entry: {
        page1: '/xxx/view/view1/view1.js',
        page2: '/xxx/view/view2/view2.js',
        index: '/xxx/view/view/index.js',
    },
    
    ...
}

这样处理的话每次新加页面都需要配置,我们可以通过读取并解析路径来操作,这里就需要使用到工具和函数来进行具体操作,我们可以在根目录新建 build 文件夹存放 entryFiles.js 这样共用的 webpack 功能性文件,并加入多入口读取解析方法:

/* entryFiles.js */
const path = require('path');

// glob 是 webpack 安装时依赖的一个第三方模块,该模块允许你使用 * 等符号,
// 例如 lib/*.js 就是获取 lib 文件夹下的所有 js 后缀名的文件
const glob = require('glob');

// 取得相应的页面路径,因为之前的配置,所以是 src 文件夹下的 pages 文件夹
const PAGE_PATH = path.resolve(__dirname, '../src/views');

/* 
* 多入口配置
* 通过 glob 模块读取 pages 文件夹下的所有对应文件夹下的 js * 后缀文件,如果该文件存在
* 那么就作为入口处理
*/
exports.getEntries = () => {
    let entryFiles = glob.sync(PAGE_PATH + '/*/*.js') // 同步读取所有入口文件
    let map = {}
    
    // 遍历所有入口文件
    entryFiles.forEach(filePath => {
        // 获取文件名
        let filename = filePath.substring(filePath.lastIndexOf('/') + 1, filePath.lastIndexOf('.'))
        
        // 以键值对的形式存储
        map[filename] = filePath 
    })
    
    return map
}

上方我们使用了 glob 这一第三方模块读取所有 views 文件夹下的入口文件,其需要进行安装:

npm install glob -d

下面是使用gold读取文件目录的流程图:

image.png

到这里我们就完成之前需要手动配置的操作.读取并存储完毕后,我们得到了一个入口文件的对象集合,这个对象我们便可以将其设置到 webpack 的 entry 属性上,这里我们需要修改 vue.config.js 的配置来间接修改 webpack 的值:


/* vue.config.js */

const entryFile = require('./build/entryFiles')

module.exports = {
    ...
    
    configureWebpack: config => {
        config.entry = entryFile.getEntries()
    },
    
    ...
}

到这里,我们的多入口就算完成了.

三,'乾坤大挪移',修改单页面为多模板

相对于多入口来说,多模板的配置也是大同小异,这里所说的模板便是每个 page 下的 html 模板文件,而模板文件的作用主要用于 webpack 中 html-webpack-plugin 插件的配置,其会根据模板文件生产一个编译后的 html 文件并自动加入携带 hash 的脚本和样式,基本配置如下:

/* webpack 配置文件 */
const HtmlWebpackPlugin = require('html-webpack-plugin') // 安装后引用插件

module.exports = {
    ...
    
    plugins: [
        new HtmlWebpackPlugin({
            title: 'My Page', // 生成 html 中的 title
            filename: 'demo.html', // 生成 html 的文件名
            template: 'xxx/xxx/demo.html', // 模板路径
            chunks: ['manifest', 'vendor', 'demo'], // 所要包含的模块
            inject: true, // 是否注入资源
        })
    ]
    
    ...
}

以上是单模板的配置,那么如果是多模板只要继续往 plugins 数组中添加 HtmlWebpackPlugin 即可,但是为了和多入口一样能够灵活的获取 pages 目录下所有模板文件并进行配置,我们可以在bulid文件夹中新建htmlPlugin.js文件 ,添加多模板的读取解析方法:

/* htmlPlugin.js */

// 多页面输出配置
// 与上面的多页面入口配置相同,读取 page 文件夹下的对应的 html 后缀文件,然后放入数组中
exports.htmlPlugin = configs => {
    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', // 生成 html 的文件名
            chunks: ['manifest', 'vendor',  filename],
            inject: true,
        }
        
        // 如果有自定义配置可以进行 merge
        if (configs) {
            conf = merge(conf, configs)
        }
        
        // 针对生产环境配置
        if (process.env.NODE_ENV === 'production') {
            conf = merge(conf, {
                minify: {
                    removeComments: true, // 删除 html 中的注释代码
                    collapseWhitespace: true, // 删除 html 中的空白符
                    // removeAttributeQuotes: true // 删除 html 元素中属性的引号
                },
                chunksSortMode: 'manual' // 按 manual 的顺序引入
            })
        }
        
        arr.push(new HtmlWebpackPlugin(conf))
    })
    
    return arr
}


上面我们仍然是使用 glob 读取所有模板文件,然后将其遍历并设置每个模板的 config,同时针对一些自定义配置和生产环境的配置进行了 merge 处理

这里介绍一下生产环境下 minify 配置的作用:将 html-minifier 的选项作为对象来缩小输出

html-minifier 是一款用于缩小 html 文件大小的工具,其有很多配置项功能,包括上述所列举的常用的删除注释、空白、引号等。 下面贴上一张glob读取模板文件的流程图:

image.png

当我们编写完了多模板的方法后,我们同样可以在 vue.config.js 中进行配置,与多入口不同的是我们在 configureWebpack 中不能直接替换 plugins 的值,那需要这么操作?

/* vue.config.js */

const htmlPlugin = require('./build/htmlPlugin')
const entryFile = require('./build/entryFiles')

module.exports = {
    ...
    
    configureWebpack: config => {
        config.entry = utils.getEntrie() // 直接覆盖 entry 配置
        
        // 使用 return 一个对象会通过 webpack-merge 进行合并,plugins 不会置空
        return {
            plugins: [...htmlPlugin.htmlPlugin()]
        }
    },
    
    ...
}

至此,我们多页应用的多入口和多模板的配置就完成了,这时候我们运行命令 npm run build 后你会发现 dist 目录下生成了 3 个 html 文件,分别是 index.html、view1.html 和 view2.html。

四,'另有蹊跷',通过pages 配置

其实,在 vue.config.js 中,我们还有一个配置没有使用,便是 pages。pages 对象允许我们为应用配置多个入口及模板,这就为我们的多页应用提供了开放的配置入口。官方示例代码:

/* vue.config.js */
module.exports = {
    pages: {
        index: {
            // page 的入口
            entry: 'src/index/main.js',
            // 模板来源
            template: 'public/index.html',
            // 在 dist/index.html 的输出
            filename: 'index.html',
            // 当使用 title 选项时,
            // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
            title: 'Index Page',
            // 在这个页面中包含的块,默认情况下会包含
            // 提取出来的通用 chunk 和 vendor chunk。
            chunks: ['chunk-vendors', 'chunk-common', 'index']
        },
        // 当使用只有入口的字符串格式时,
        // 模板会被推导为 `public/subpage.html`
        // 并且如果找不到的话,就回退到 `public/index.html`。
        // 输出文件名会被推导为 `subpage.html`。
        subpage: 'src/subpage/main.js'
    }
}

我们不难发现,pages 对象中的 key 就是入口的别名,而其 value 对象其实是入口 entry 和模板属性的合并,这样我们上述介绍的获取多入口和多模板的方法就可以合并成一个函数来进行多页的处理,合并后的 setPages 方法如下:

// pages 多入口配置
exports.setPages = configs => {
    let entryFiles = glob.sync(PAGE_PATH + '/*/*.js')
    let map = {}

    entryFiles.forEach(filePath => {
        let filename = filePath.substring(filePath.lastIndexOf('/') + 1, filePath.lastIndexOf('.'))
        let tmp = filePath.substring(0, filePath.lastIndexOf('/'))

        let conf = {
            // page 的入口
            entry: filePath, 
            // 模板来源
            template: tmp + '.html', 
            // 在 dist/index.html 的输出
            filename: filename + '.html', 
            // 页面模板需要加对应的js脚本,如果不加这行则每个页面都会引入所有的js脚本
            chunks: ['manifest', 'vendor', filename], 
            inject: true,
        };

        if (configs) {
            conf = merge(conf, configs)
        }

        if (process.env.NODE_ENV === 'production') {
            conf = merge(conf, {
                minify: {
                    removeComments: true, // 删除 html 中的注释代码
                    collapseWhitespace: true, // 删除 html 中的空白符
                    // removeAttributeQuotes: true // 删除 html 元素中属性的引号
                },
                chunksSortMode: 'manual'// 按 manual 的顺序引入
            })
        }

        map[filename] = conf
    })

    return map
}

return 出的 map 对象就是 pages 所需要的配置项结构,我们只需在 vue.config.js 中引用:

/* vue.config.js */

const utils = require('./build/utils')

module.exports = {
    ...
    
    pages: utils.setPages(),
    
    ...
}

这样我们多页应用基于 pages 配置的改进就大功告成了,当你运行打包命令来查看输出结果的时候,你会发现和之前的方式相比并没有什么变化,这就说明这两种方式都适用于多页的构建,但是这里还是推荐大家使用更便捷的 pages 配置。