用vue構建多頁面應用的示例代碼
最近一直在研究使用vue做出來一些東西,但都是SPA的單頁面應用,但實際工作中,單頁面并不一定符合業(yè)務需求,所以這篇我就來說說怎么開發(fā)多頁面的Vue應用,以及在這個過程會遇到的問題。
這是我放在GitHub上的項目,里面有整個配置文件,可以參看一下:multiple-vue-page
準備工作
在本地用vue-cli新建一個項目,這個步驟vue的官網(wǎng)上有,我就不再說了。
這里有一個地方需要改一下,在執(zhí)行npm install命令之前,在package.json里添加一個依賴,后面會用到。

修改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后綴文件,然后放入數(shù)組中
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
}
/* 這里是添加的部分 ---------------------------- 結束 */
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]')
}
}
]
}
}
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
/* 注釋這個區(qū)域的文件 ------------- 開始 */
// new HtmlWebpackPlugin({
// filename: 'index.html',
// template: 'index.html',
// inject: true
// }),
/* 注釋這個區(qū)域的文件 ------------- 結束 */
new FriendlyErrorsPlugin()
/* 添加 .concat(utils.htmlPlugin()) ------------------ */
].concat(utils.htmlPlugin())
})
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
/* 注釋這個區(qū)域的內容 ---------------------- 開始 */
// 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'
// }),
/* 注釋這個區(qū)域的內容 ---------------------- 結束 */
// 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
至此,webpack的配置就結束了。
但是還沒完啦,下面繼續(xù)。
文件結構
├── 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分別是靜態(tài)資源文件、組件文件、頁面文件。
前兩個就不多說,主要是頁面文件里,我目前是按照項目的模塊分的文件夾,你也可以按照你自己的需求調整。然后在每個模塊里又有三個內容:vue文件,js文件和html文件。這三個文件的作用就相當于做spa單頁面應用時,根目錄的index.html頁面模板,src文件下的main.js和app.vue的功能。
原先,入口文件只有一個main.js,但現(xiàn)在由于是多頁面,因此入口頁面多了,我目前就是兩個:index和cell,之后如果打包,就會在dist文件下生成兩個HTML文件:index.html和cell.html(可以參考一下單頁面應用時,打包只會生成一個index.html,區(qū)別在這里)。
cell文件下的三個文件,就是一般模式的配置,參考index的就可以,但并不完全相同。
特別注意的地方
cell.js
在這個文件里,按照寫法,應該是這樣的吧:
import Vue from 'Vue'
import cell from './cell.vue'
new Vue({
el:'#app',// 這里參考cell.html和cell.vue的根節(jié)點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>)
網(wǎng)上的解釋是這樣的:
運行時構建不包含模板編譯器,因此不支持 template 選項,只能用 render 選項,但即使使用運行時構建,在單文件組件中也依然可以寫模板,因為單文件組件的模板會在構建時預編譯為 render 函數(shù)。運行時構建比獨立構建要輕量30%,只有 17.14 Kb min+gzip大小。
上面一段是官方api中的解釋。就是說,如果我們想使用template,我們不能直接在客戶端使用npm install之后的vue。
也給出了相應的修改方案:
resolve: { alias: { 'vue': 'vue/dist/vue.js' } }
這里是修改package.json的resolve下的vue的配置,很多人反應這樣修改之后就好了,但是我按照這個方法修改之后依然報錯。然后我就想到上面提到的render函數(shù),因此我的修改是針對cell.js文件的。
import Vue from 'Vue'
import cell from './cell.vue'
/* eslint-disable no-new */
new Vue({
el: '#app',
render: h => h(cell)
})
這里面我用render函數(shù)取代了組件的寫法,在運行就沒問題了。
頁面跳轉
既然是多頁面,肯定涉及頁面之間的互相跳轉,就按照我這個項目舉例,從index.html文件點擊a標簽跳轉到cell.html。
我最開始寫的是:
<!-- index.html --> <a href='../cell/cell.html'></a>
但這樣寫,不論是在開發(fā)環(huán)境還是最后測試,都會報404,找不到這個頁面。
改成這樣既可:
<!-- index.html --> <a href='cell.html'></a>
這樣他就會自己找cell.html這個文件。
打包后的資源路徑
執(zhí)行npm run build之后,打開相應的html文件你是看不到任何東西的,查看原因是找不到相應的js文件和css文件。
這時候的文件結構是這樣的:
├── dist │ ├── js │ ├── css │ ├── index.html │ └── cell.html
查看index.html文件之后會發(fā)現(xiàn)資源的引用路徑是:
/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 run build看看吧。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
vue項目Network: unavailable的問題及解決
這篇文章主要介紹了vue項目Network: unavailable的問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09
vue報錯Error:Cannot?find?module?'fs/promises'的解決方
最近的項目需要用到vue/cli,但是用cnpm安裝vue/cli的時候報錯了,下面這篇文章主要給大家介紹了關于vue報錯Error:Cannot?find?module?'fs/promises'的解決方式,需要的朋友可以參考下2022-11-11
Vue實現(xiàn)Hover功能(mouseover與mouseenter的區(qū)別及說明)
這篇文章主要介紹了Vue實現(xiàn)Hover功能(mouseover與mouseenter的區(qū)別及說明),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10

