最近一直在研究使用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 試試看