最近一直在研究使用vue作出来一些东西,但都是SPA的单页面应用,但实际工做中,单页面并不必定符合业务需求,因此这篇我就来讲说怎么开发多页面的Vue应用,以及在这个过程会遇到的问题。css
在本地用vue-cli
新建一个项目,这个步骤vue的官网上有,我就再也不说了。html
这里有一个地方须要改一下,在执行npm install
命令以前,在package.json
里添加一个依赖,后面会用到。vue
这里展现一下个人项目目录node
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
文件下,分别是:webpack
我就按照顺序放出完整的文件内容,而后在作修改或添加的位置用注释符标注出来:git
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 /* 这里是添加的部分 ---------------------------- 结束 */
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 }
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的配置就结束了。github
可是还没完啦,下面继续。web
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-cli
前两个就很少说,主要是页面文件里,我目前是按照项目的模块分的文件夹,你也能够按照你本身的需求调整。而后在每一个模块里又有三个内容:vue文件,js文件和html文件。这三个文件的做用就至关于作spa单页面应用时,根目录的index.html
页面模板,src文件下的main.js
和app.vue
的功能。npm
原先,入口文件只有一个main.js,但如今因为是多页面,所以入口页面多了,我目前就是两个:index和cell,以后若是打包,就会在dist
文件下生成两个HTML文件:index.html
和cell.html
(能够参考一下单页面应用时,打包只会生成一个index.html,区别在这里)。
cell文件下的三个文件,就是通常模式的配置,参考index的就能够,但并不彻底相同。
在这个文件里,按照写法,应该是这样的吧:
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 试试看