vue多页面应用配置

目录结构

使用vue-cli 3.0构建

${root}/
    build/
        utils.js
    public/
        index.html
    src/
        assets/
            css/
            img/
        components/
            index.js
        pages/
            list/
                index.html
                index.js
                index.vue
            details/
                index.html
                index.js
                index.vue
        utils/
        store.js
    ......
    vue.confug.js

主要代码

src/page/*/inedx.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="format-detection" content="telephone=no, email=no" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
    <meta name="format-detection" content="telephone=no, address=no, email=no" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="author" content="duminghong" />
    <title>友帮</title>
    <% for (var chunk in htmlWebpackPlugin.files.css) { %>
        <% if(htmlWebpackPlugin.files.css[chunk]) {%>
            <link href="<%= htmlWebpackPlugin.files.css[chunk] %>" rel="stylesheet" />
        <%}%>
    <% } %>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->

    <% for (var chunk in htmlWebpackPlugin.files.js) { %>
        <% if(htmlWebpackPlugin.files.js[chunk]) {%>
            <script type="text/javascript" src="<%= htmlWebpackPlugin.files.js[chunk] %>"></script>
        <%}%>
    <% } %>
  </body>
</html>
src/page/*/inedx.js
import Vue from 'vue'
import App from './index.vue'
import store from '@/store';

new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

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

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

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

// 用于做相应的merge处理
const merge = require('webpack-merge')

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

// 多入口配置
// 通过glob模块读取page文件夹下的所有对应文件夹下的js后缀文件,如果该文件存在
// 那么就作为入口处理
exports.getEntries = () => {
    let entryFiles = glob.sync(PAGE_PATH + '/*/index.js')
    let map = {}

    entryFiles.forEach(filePath => {
        var _path = filePath.substring(0,filePath.lastIndexOf('\/index.'))
        var filename = _path.substring(_path.lastIndexOf('\/')+1)
        map[filename] = filePath
    })

    return map
}


// 多页面输出配置
// 与上面的多页面入口配置相同,读取page文件夹下的对应的html后缀文件,然后放入数组中
exports.htmlPlugin = configs => {
    let entryHtml = glob.sync(PAGE_PATH + '/*/index.html')
    let arr = []

    entryHtml.forEach(filePath => {
        var _path = filePath.substring(0,filePath.lastIndexOf('\/index.'))
        var filename = _path.substring(_path.lastIndexOf('\/')+1)

        let conf = {
            multihtmlCache: true,
            // 模板来源
            template: filePath,
            // 文件名称
            filename: filename + '.html',
            // 页面模板需要加对应的js脚本,如果不加这行则每个页面都会引入所有的js脚本
            // chunks: ['manifest', 'vendor',  filename],
            chunks: ['chunk-vendors', 'chunk-common', filename],
            inject: false,
        }

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

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

    return arr
}

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

    entryFiles.forEach(filePath => {
        var _path = filePath.substring(0,filePath.lastIndexOf('\/index.'))
        var filename = _path.substring(_path.lastIndexOf('\/')+1)

        let tmp = _path+'/'+filename;

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

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

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

        map[filename] = conf;
    })

    return map
}
vue.confug.js
const path = require('path')
const utils = require('./build/utils')

// 用于做相应的merge处理
const merge = require('webpack-merge')

const resolve = dir => {
    return path.join(__dirname, dir)
}
module.exports = {
    // 项目二级目录
    publicPath: './',
    // 编译输出目录
    outputDir: resolve('../../../build/app/module'),
    // 生产环境构建生成 source map
    productionSourceMap: true,
    chainWebpack: config => {
        config.module
            .rule('images')
            .use('url-loader')
            .tap(options =>
                merge(options, {
                  limit: 5120,
                })  
            )
        config.resolve.alias
            .set('@', resolve('src'))
            .set('@img', resolve('src/assets/img'))
    },
    // 本地服务器
    devServer: {
        open: true, // 是否自动打开浏览器页面
        // ......
    },
    // 多入口
    configureWebpack: config => {
        config.entry = utils.getEntries() // 直接覆盖 entry 配置
        
        // 使用 return 一个对象会通过 webpack-merge 进行合并,plugins 不会置空
        return {
            plugins: [...utils.htmlPlugin()]
        }
    },
    // pages 多入口配置
    pages: utils.setPages(),
}

编译之后的目录

${root}/
    css/
        chunk-common.xxx.css
        chunk-vendors.xxx.css
        list.xxx.css
        details.xxx.css
    img/
        image.xxx.png
        ......
    js/
        chunk-common.xxx.js
        chunk-vendors.xxx.js
        list.xxx.js
        details.xxx.js
    list.html
    details.html

后记

页面多了编译会很慢,所以在开发的时候指定编译,修改如下

  1. ${root}目录下新增buildPages/index.js
const path = require('path')
const fs = require("fs");

const glob = require('glob')

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

let entryFiles = glob.sync(PAGE_PATH + '/*')
let arr = 'exports.pages=[\n';
entryFiles.forEach(v => {
    arr+=`  "${v.substring(v.lastIndexOf('\/') + 1)}",\n`;
})
arr+=']';
// entryFiles.map( v => v.substring(v.lastIndexOf('\/') + 1))
console.log(arr)
fs.writeFile(path.resolve(__dirname, '../build/index.js'), arr, function(err){
    if (err) {
        return console.error(err);
    }
    console.log("目录写入成功!");
})
  1. build/utils.js修改为
const path = require('path')
const _build = require('./index')

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

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

// 用于做相应的merge处理
const merge = require('webpack-merge')

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

// 多入口配置
// 通过glob模块读取page文件夹下的所有对应文件夹下的js后缀文件,如果该文件存在
// 那么就作为入口处理
exports.getEntries = () => {
    let entryFiles = [];
    if(process.env.NODE_ENV === 'production'){
        entryFiles = glob.sync(PAGE_PATH + '/*/*.js')
    }else{
        _build.pages.forEach(page => {
            console.log(page);
            entryFiles.push(`${PAGE_PATH}/${page}/${page}.js`)
        })
    }
    let map = {}

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

        map[filename] = filePath
    })

    return map
}


// 多页面输出配置
// 与上面的多页面入口配置相同,读取page文件夹下的对应的html后缀文件,然后放入数组中
exports.htmlPlugin = configs => {
    let entryHtml = [];
    if(process.env.NODE_ENV === 'production'){
        entryHtml = glob.sync(PAGE_PATH + '/*/*.html')
    }else{
        _build.pages.forEach(page => {
            entryHtml.push(`${PAGE_PATH}/${page}/${page}.html`)
        })
    }
    let arr = []

    entryHtml.forEach(filePath => {
        let filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'))

        let conf = {
            multihtmlCache: true,
            // 模板来源
            template: filePath,
            // 文件名称
            filename: filename + '.html',
            // 页面模板需要加对应的js脚本,如果不加这行则每个页面都会引入所有的js脚本
            // chunks: ['manifest', 'vendor',  filename],
            chunks: ['chunk-vendors', 'chunk-common', filename],
            inject: false,
        }

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

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

    return arr
}

// pages 多入口配置
exports.setPages = configs => {
    let entryFiles = [];
    if(process.env.NODE_ENV === 'production'){
        entryFiles = glob.sync(PAGE_PATH + '/*/*.js')
    }else{
        _build.pages.forEach(page => {
            console.log(page)
            entryFiles.push(`${PAGE_PATH}/${page}/${page}.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], 
            chunks: ['chunk-vendors', 'chunk-common', filename], 
            inject: false,
        };

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

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

        map[filename] = conf;
    })

    return map
}
  1. package.jsonscripts新增"page": "node ./buildPages/index.js"
{
  ...
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "page": "node ./buildPages/index.js"
  },
  ...
}
  1. 在第一次运行前先执行npm run page
  2. 在生成的build/index.js里把不用编译的注释掉
  3. 执行npm run serve开发

推荐阅读更多精彩内容