zoukankan      html  css  js  c++  java
  • 用vue构建多页面应用

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

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

    准备工作

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

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

    添加package.json新增的文件

      "devDependencies": {
        "autoprefixer": "^7.1.2",
        "babel-core": "^6.22.1",
        "babel-helper-vue-jsx-merge-props": "^2.0.3",
        "babel-loader": "^7.1.1",
        "babel-plugin-syntax-jsx": "^6.18.0",
        "babel-plugin-transform-runtime": "^6.22.0",
        "babel-plugin-transform-vue-jsx": "^3.5.0",
        "babel-preset-env": "^1.3.2",
        "babel-preset-stage-2": "^6.22.0",
        "chalk": "^2.0.1",
    
        "connect-history-api-fallback": "^1.3.0",
        "cssnano": "^3.10.0",
            "eventsource-polyfill": "^0.9.6",
            "express": "^4.14.1",
            "http-proxy-middleware": "^0.17.3",
            "opn": "^5.1.0",
            "webpack-dev-middleware": "^1.10.0",
          "webpack-hot-middleware": "^2.18.0",
      
        "copy-webpack-plugin": "^4.0.1",
        "css-loader": "^0.28.0",
        "d3": "^5.16.0",
        "extract-text-webpack-plugin": "^3.0.0",
        "file-loader": "^1.1.4",
        "friendly-errors-webpack-plugin": "^1.6.1",
        "html-webpack-plugin": "^2.30.1",
        "node-notifier": "^5.1.2",
        "optimize-css-assets-webpack-plugin": "^3.2.0",
        "ora": "^1.2.0",
        "portfinder": "^1.0.13",
        "postcss-import": "^11.0.0",
        "postcss-loader": "^2.0.8",
        "postcss-url": "^7.2.1",
        "rimraf": "^2.6.0",
        "semver": "^5.3.0",
        "shelljs": "^0.7.6",
        "uglifyjs-webpack-plugin": "^1.1.1",
        "url-loader": "^0.5.8",
        "vue-loader": "^13.3.0",
        "vue-style-loader": "^3.0.1",
        "vue-template-compiler": "^2.5.2",
        "webpack": "^3.6.0",
        "webpack-bundle-analyzer": "^2.9.0",
        "webpack-dev-server": "^2.9.1",
        "webpack-merge": "^4.1.0",
            "glob": "^7.0.3"
      },
    View Code

    修改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
    }
    /* 这里是添加的部分 ---------------------------- 结束 */
    View Code

    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]')
          }
        }
        ]
      }
    }
    View Code

    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())
    })
    View Code

    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
    View Code

    至此,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包的过程中可能遇到的问题

    VUE启动报错ERROR:CANNOT FIND MODULE 'ARRAY-INCLUDES'

    解决办法:

    删除项目中的node_modules文件夹

    清空所有依赖,cnpm cache clean

    重新安装依赖,cpnm install

    重新启动就可以了

  • 相关阅读:
    Python流程控制
    Python 迭代器和列表解析
    Python 文件对象
    TF-IDF介绍
    hexo博客更换主题
    学习笔记—MapReduce
    Mac下Anaconda的安装和使用
    Flume的介绍和简单操作
    hexo+github搭建个人博客
    Hbase的安装和基本使用
  • 原文地址:https://www.cnblogs.com/lst619247/p/13385989.html
Copyright © 2011-2022 走看看