用vue构建多页面应用

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

准备工作

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

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

修改webpack配置

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

 1 ├── README.md
 2 ├── build
 3 │   ├── build.js
 4 │   ├── check-versions.js
 5 │   ├── dev-client.js
 6 │   ├── dev-server.js
 7 │   ├── utils.js
 8 │   ├── vue-loader.conf.js
 9 │   ├── webpack.base.conf.js
10 │   ├── webpack.dev.conf.js
11 │   └── webpack.prod.conf.js
12 ├── config
13 │   ├── dev.env.js
14 │   ├── index.js
15 │   └── prod.env.js
16 ├── package.json
17 ├── src
18 │   ├── assets
19 │   │   └── logo.png
20 │   ├── components
21 │   │   ├── Hello.vue
22 │   │   └── cell.vue
23 │   └── pages
24 │       ├── cell
25 │       │   ├── cell.html
26 │       │   ├── cell.js
27 │       │   └── cell.vue
28 │       └── index
29 │           ├── index.html
30 │           ├── index.js
31 │           ├── index.vue
32 │           └── router
33 │               └── index.js
34 └── static

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

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

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

utils.js文件

  1 // utils.js文件
  2 
  3 var path = require('path')
  4 var config = require('../config')
  5 var ExtractTextPlugin = require('extract-text-webpack-plugin')
  6 
  7 exports.assetsPath = function (_path) {
  8     var assetsSubDirectory = process.env.NODE_ENV === 'production' ?
  9         config.build.assetsSubDirectory :
 10         config.dev.assetsSubDirectory
 11     return path.posix.join(assetsSubDirectory, _path)
 12 }
 13 
 14 exports.cssLoaders = function (options) {
 15     options = options || {}
 16 
 17     var cssLoader = {
 18         loader: 'css-loader',
 19         options: {
 20             minimize: process.env.NODE_ENV === 'production',
 21             sourceMap: options.sourceMap
 22         }
 23     }
 24 
 25     // generate loader string to be used with extract text plugin
 26     function generateLoaders(loader, loaderOptions) {
 27         var loaders = [cssLoader]
 28         if (loader) {
 29             loaders.push({
 30                 loader: loader + '-loader',
 31                 options: Object.assign({}, loaderOptions, {
 32                     sourceMap: options.sourceMap
 33                 })
 34             })
 35         }
 36 
 37         // Extract CSS when that option is specified
 38         // (which is the case during production build)
 39         if (options.extract) {
 40             return ExtractTextPlugin.extract({
 41                 use: loaders,
 42                 fallback: 'vue-style-loader'
 43             })
 44         } else {
 45             return ['vue-style-loader'].concat(loaders)
 46         }
 47     }
 48 
 49     // https://vue-loader.vuejs.org/en/configurations/extract-css.html
 50     return {
 51         css: generateLoaders(),
 52         postcss: generateLoaders(),
 53         less: generateLoaders('less'),
 54         sass: generateLoaders('sass', { indentedSyntax: true }),
 55         scss: generateLoaders('sass'),
 56         stylus: generateLoaders('stylus'),
 57         styl: generateLoaders('stylus')
 58     }
 59 }
 60 
 61 // Generate loaders for standalone style files (outside of .vue)
 62 exports.styleLoaders = function (options) {
 63     var output = []
 64     var loaders = exports.cssLoaders(options)
 65     for (var extension in loaders) {
 66         var loader = loaders[extension]
 67         output.push({
 68             test: new RegExp('\.' + extension + '$'),
 69             use: loader
 70         })
 71     }
 72     return output
 73 }
 74 
 75 /* 这里是添加的部分 ---------------------------- 开始 */
 76 
 77 // glob是webpack安装时依赖的一个第三方模块,还模块允许你使用 *等符号, 例如lib/*.js就是获取lib文件夹下的所有js后缀名的文件
 78 var glob = require('glob')
 79 // 页面模板
 80 var HtmlWebpackPlugin = require('html-webpack-plugin')
 81 // 取得相应的页面路径,因为之前的配置,所以是src文件夹下的pages文件夹
 82 var PAGE_PATH = path.resolve(__dirname, '../src/pages')
 83 // 用于做相应的merge处理
 84 var merge = require('webpack-merge')
 85 
 86 
 87 //多入口配置
 88 // 通过glob模块读取pages文件夹下的所有对应文件夹下的js后缀文件,如果该文件存在
 89 // 那么就作为入口处理
 90 exports.entries = function () {
 91     var entryFiles = glob.sync(PAGE_PATH + '/*/*.js')
 92     var map = {}
 93     entryFiles.forEach((filePath) => {
 94         var filename = filePath.substring(filePath.lastIndexOf('/') + 1, filePath.lastIndexOf('.'))
 95         map[filename] = filePath
 96     })
 97     return map
 98 }
 99 
100 //多页面输出配置
101 // 与上面的多页面入口配置相同,读取pages文件夹下的对应的html后缀文件,然后放入数组中
102 exports.htmlPlugin = function () {
103     let entryHtml = glob.sync(PAGE_PATH + '/*/*.html')
104     let arr = []
105     entryHtml.forEach((filePath) => {
106         let filename = filePath.substring(filePath.lastIndexOf('/') + 1, filePath.lastIndexOf('.'))
107         let conf = {
108             // 模板来源
109             template: filePath,
110             // 文件名称
111             filename: filename + '.html',
112             // 页面模板需要加对应的js脚本,如果不加这行则每个页面都会引入所有的js脚本
113             chunks: ['manifest', 'vendor', filename],
114             inject: true
115         }
116         if (process.env.NODE_ENV === 'production') {
117             conf = merge(conf, {
118                 minify: {
119                     removeComments: true,
120                     collapseWhitespace: true,
121                     removeAttributeQuotes: true
122                 },
123                 chunksSortMode: 'dependency'
124             })
125         }
126         arr.push(new HtmlWebpackPlugin(conf))
127     })
128     return arr
129 }
130 /* 这里是添加的部分 ---------------------------- 结束 */

webpack.base.conf.js 文件

 1 // webpack.base.conf.js 文件
 2 
 3 var path = require('path')
 4 var utils = require('./utils')
 5 var config = require('../config')
 6 var vueLoaderConfig = require('./vue-loader.conf')
 7 
 8 function resolve(dir) {
 9   return path.join(__dirname, '..', dir)
10 }
11 
12 module.exports = {
13   /* 修改部分 ---------------- 开始 */
14   entry: utils.entries(),
15   /* 修改部分 ---------------- 结束 */
16   output: {
17     path: config.build.assetsRoot,
18     filename: '[name].js',
19     publicPath: process.env.NODE_ENV === 'production' ?
20       config.build.assetsPublicPath :
21       config.dev.assetsPublicPath
22   },
23   resolve: {
24     extensions: ['.js', '.vue', '.json'],
25     alias: {
26       'vue$': 'vue/dist/vue.esm.js',
27       '@': resolve('src'),
28       'pages': resolve('src/pages'),
29       'components': resolve('src/components')
30     }
31   },
32   module: {
33     rules: [{
34       test: /.vue$/,
35       loader: 'vue-loader',
36       options: vueLoaderConfig
37     },
38     {
39       test: /.js$/,
40       loader: 'babel-loader',
41       include: [resolve('src'), resolve('test')]
42     },
43     {
44       test: /.(png|jpe?g|gif|svg)(?.*)?$/,
45       loader: 'url-loader',
46       options: {
47         limit: 10000,
48         name: utils.assetsPath('img/[name].[hash:7].[ext]')
49       }
50     },
51     {
52       test: /.(woff2?|eot|ttf|otf)(?.*)?$/,
53       loader: 'url-loader',
54       options: {
55         limit: 10000,
56         name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
57       }
58     }
59     ]
60   }
61 }

webpack.dev.conf.js 文件

  1 var utils = require('./utils')
  2 var webpack = require('webpack')
  3 var config = require('../config')
  4 var merge = require('webpack-merge')
  5 var baseWebpackConfig = require('./webpack.base.conf')
  6 var HtmlWebpackPlugin = require('html-webpack-plugin')
  7 var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
  8 
  9 // add hot-reload related code to entry chunks
 10 Object.keys(baseWebpackConfig.entry).forEach(function (name) {
 11   baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
 12 })
 13 
 14 module.exports = merge(baseWebpackConfig, {
 15   module: {
 16     rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
 17   },
 18   // cheap-module-eval-source-map is faster for development
 19   devtool: '#cheap-module-eval-source-map',
 20   plugins: [
 21     new webpack.DefinePlugin({
 22       'process.env': config.dev.env
 23     }),
 24     // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
 25     new webpack.HotModuleReplacementPlugin(),
 26     new webpack.NoEmitOnErrorsPlugin(),
 27     // https://github.com/ampedandwired/html-webpack-plugin
 28     /* 注释这个区域的文件 ------------- 开始 */
 29     // new HtmlWebpackPlugin({
 30     //   filename: 'index.html',
 31     //   template: 'index.html',
 32     //   inject: true
 33     // }),
 34     /* 注释这个区域的文件 ------------- 结束 */
 35     new FriendlyErrorsPlugin()
 36 
 37     /* 添加 .concat(utils.htmlPlugin()) ------------------ */
 38   ].concat(utils.htmlPlugin())
 39 })
 40 webpack.prod.conf.js 文件
 41 var path = require('path')
 42 var utils = require('./utils')
 43 var webpack = require('webpack')
 44 var config = require('../config')
 45 var merge = require('webpack-merge')
 46 var baseWebpackConfig = require('./webpack.base.conf')
 47 var CopyWebpackPlugin = require('copy-webpack-plugin')
 48 var HtmlWebpackPlugin = require('html-webpack-plugin')
 49 var ExtractTextPlugin = require('extract-text-webpack-plugin')
 50 var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
 51 
 52 var env = config.build.env
 53 
 54 var webpackConfig = merge(baseWebpackConfig, {
 55   module: {
 56     rules: utils.styleLoaders({
 57       sourceMap: config.build.productionSourceMap,
 58       extract: true
 59     })
 60   },
 61   devtool: config.build.productionSourceMap ? '#source-map' : false,
 62   output: {
 63     path: config.build.assetsRoot,
 64     filename: utils.assetsPath('js/[name].[chunkhash].js'),
 65     chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
 66   },
 67   plugins: [
 68     // http://vuejs.github.io/vue-loader/en/workflow/production.html
 69     new webpack.DefinePlugin({
 70       'process.env': env
 71     }),
 72     new webpack.optimize.UglifyJsPlugin({
 73       compress: {
 74         warnings: false
 75       },
 76       sourceMap: true
 77     }),
 78     // extract css into its own file
 79     new ExtractTextPlugin({
 80       filename: utils.assetsPath('css/[name].[contenthash].css')
 81     }),
 82     // Compress extracted CSS. We are using this plugin so that possible
 83     // duplicated CSS from different components can be deduped.
 84     new OptimizeCSSPlugin({
 85       cssProcessorOptions: {
 86         safe: true
 87       }
 88     }),
 89     // generate dist index.html with correct asset hash for caching.
 90     // you can customize output by editing /index.html
 91     // see https://github.com/ampedandwired/html-webpack-plugin
 92 
 93     /* 注释这个区域的内容 ---------------------- 开始 */
 94     // new HtmlWebpackPlugin({
 95     //   filename: config.build.index,
 96     //   template: 'index.html',
 97     //   inject: true,
 98     //   minify: {
 99     //     removeComments: true,
100     //     collapseWhitespace: true,
101     //     removeAttributeQuotes: true
102     //     // more options:
103     //     // https://github.com/kangax/html-minifier#options-quick-reference
104     //   },
105     //   // necessary to consistently work with multiple chunks via CommonsChunkPlugin
106     //   chunksSortMode: 'dependency'
107     // }),
108     /* 注释这个区域的内容 ---------------------- 结束 */
109 
110     // split vendor js into its own file
111     new webpack.optimize.CommonsChunkPlugin({
112       name: 'vendor',
113       minChunks: function (module, count) {
114         // any required modules inside node_modules are extracted to vendor
115         return (
116           module.resource &&
117           /.js$/.test(module.resource) &&
118           module.resource.indexOf(
119             path.join(__dirname, '../node_modules')
120           ) === 0
121         )
122       }
123     }),
124     // extract webpack runtime and module manifest to its own file in order to
125     // prevent vendor hash from being updated whenever app bundle is updated
126     new webpack.optimize.CommonsChunkPlugin({
127       name: 'manifest',
128       chunks: ['vendor']
129     }),
130     // copy custom static assets
131     new CopyWebpackPlugin([{
132       from: path.resolve(__dirname, '../static'),
133       to: config.build.assetsSubDirectory,
134       ignore: ['.*']
135     }])
136     /* 该位置添加 .concat(utils.htmlPlugin()) ------------------- */
137   ].concat(utils.htmlPlugin())
138 })
139 
140 if (config.build.productionGzip) {
141   var CompressionWebpackPlugin = require('compression-webpack-plugin')
142 
143   webpackConfig.plugins.push(
144     new CompressionWebpackPlugin({
145       asset: '[path].gz[query]',
146       algorithm: 'gzip',
147       test: new RegExp(
148         '\.(' +
149         config.build.productionGzipExtensions.join('|') +
150         ')$'
151       ),
152       threshold: 10240,
153       minRatio: 0.8
154     })
155   )
156 }
157 
158 if (config.build.bundleAnalyzerReport) {
159   var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
160   webpackConfig.plugins.push(new BundleAnalyzerPlugin())
161 }
162 
163 module.exports = webpackConfig

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

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

文件结构

 1 ├── src
 2 │   ├── assets
 3 │   │   └── logo.png
 4 │   ├── components
 5 │   │   ├── Hello.vue
 6 │   │   └── cell.vue
 7 │   └── pages
 8 │       ├── cell
 9 │       │   ├── cell.html
10 │       │   ├── cell.js
11 │       │   └── cell.vue
12 │       └── index
13 │           ├── index.html
14 │           ├── index.js
15 │           ├── index.vue
16 │           └── router
17 │               └── index.js

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

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

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

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

特别注意的地方

cell.js

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

1 import Vue from 'Vue'
2 import cell from './cell.vue'
3 
4 new Vue({
5     el:'#app',// 这里参考cell.html和cell.vue的根节点id,保持三者一致
6     teleplate:'<cell/>',
7     components:{ cell }
8 })

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

1 [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.
2 (found in <Root>)

网上的解释是这样的:

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

上面一段是官方api中的解释。就是说,如果我们想使用template,我们不能直接在客户端使用npm install之后的vue。

也给出了相应的修改方案:

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

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

1 import Vue from 'Vue'
2 import cell from './cell.vue'
3 
4 /* eslint-disable no-new */
5 new Vue({
6   el: '#app',
7   render: h => h(cell)
8 })

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

页面跳转

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

我最开始写的是:

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

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

改成这样既可:

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

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

打包后的资源路径

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

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

1 ├── dist
2 │   ├── js
3 │   ├── css
4 │   ├── index.html
5 │   └── cell.html

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

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

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

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

 1 build: {
 2     env: require('./prod.env'),
 3     index: path.resolve(__dirname, '../dist/index.html'),
 4     assetsRoot: path.resolve(__dirname, '../dist'),
 5     assetsSubDirectory: 'static',
 6     assetsPublicPath: '/',
 7     productionSourceMap: true,
 8     // Gzip off by default as many popular static hosts such as
 9     // Surge or Netlify already gzip all static assets for you.
10     // Before setting to `true`, make sure to:
11     // npm install --save-dev compression-webpack-plugin
12     productionGzip: false,
13     productionGzipExtensions: ['js', 'css'],
14     // Run the build command with an extra argument to
15     // View the bundle analyzer report after build finishes:
16     // `npm run build --report`
17     // Set to `true` or `false` to always turn it on or off
18     bundleAnalyzerReport: process.env.npm_config_report
19   },

将这里面的

1 assetsPublicPath: '/',

改成

1 assetsPublicPath: './',

以上内容就是实际项目运用的,这就可以啦,在重新npm run build 试试看

原文地址:https://www.cnblogs.com/ljx20180807/p/9760448.html