vue 多页面应用例子_用vue构建多页面应用
为什么要用多页面应用?
研究了下vue搭建多页面应用,个人感觉多页面应用的优势主要就是在打包的时候每个页面都是单独打包,除了公用的vendor/mainfest文件,每个页面都只引用自己的样式表和脚本文件。在一个模块更新的时候,也不会影响到其他模块(如下图)。即提高了页面加载速度,也有利于运营维护。另外有SEO需求的话,单页面应用的改造是比较麻烦的,多页面入口则容易的多。关于单页面和多页面详细对比分析详见:https://www.cnblogs.com/xyyt/p/9116827.html
如上图,只是修改了index模块下的页面,重新打包,只有index相关的css/js文件及mainfest,其他文件都没有改动。
上边的说明还不好理解?
现实生活中的集团和子公司的例子来模拟说明多页面应用是再形象不过——一个集团相当于一个大项目,集团下边每个子公司都负责一块儿独立的业务(相当于每个页面入口)。集团提供了办公场地、办公设备、人员招聘等公共资源,每个子公司又可以根据自己的需要调整自己内部组织结构,置办自己独有的一些内部资源。每个子公司都相互独立运行,一个子公司的变动不会影响到其他子公司。就算后边某个子公司发展足够好可以从集团独立出来,或者经营不善面临解散,也只是子公司内部及集团层面有调整,对其他子公司没任何影响,多页面应用的优势也就是这样。
怎么搭建多页面应用?
首先,你需要会使用vue-cli脚手架创建一个基于webpack模板的单页面应用项目,需要注意的地方请留意下面代码后边的注释:
# 全局安装 vue-cli
npminstall --global vue-cli
# 创建一个基于 webpack 模板的新项目
vue init webpack my-project
# 这里需要进行一些配置,默认回车即可
This willinstall Vue 2.x version of the template.
For Vue1.x use: vue init webpack#1.0 my-project? Project name my-project?Project description A Vue.js project? Author runoob
?Vue build standalone? Use ESLint to lint your code? Yes//如不需要eslint,此处输入N回车
? Pick an ESLint preset Standard//如不需要eslint,此处输入N回车
? Setup unit tests with Karma + Mocha? Yes//如不需要单元测试,此处输入N回车
? Setup e2e tests with Nightwatch? Yes//如不需要端对端测试,此处输入N回车
vue-cli · Generated "my-project".
To get started:
cd my-project
npminstallnpm run dev
Documentation can be found at https://vuejs-templates.github.io/webpack
然后,进入项目,安装并运行,确保单页面应用能正常访问:
cd my-project
npm install
npm run dev
DONE Compiled successfullyin4388ms> Listening at http://localhost:8080
注意:目前最新的模板是没有开启自动打开浏览器访问的,可以去config/index.js中开启——autoOpenBrowser: true
最后,就要进入正题了,开始进行多页面改造了。
单页面应用改造多页面应用:
1.准备工作_安装glob组件:
glob是webpack安装时依赖的一个第三方模块,该模块允许你使用 *等符号, 例如lib/*.js就是获取lib文件夹下的所有js后缀名的文件。
npm install glob -save-dev
2. 配置文件改造:
需要改动的文件如下:
下面就按照顺序贴出完整的代码内容,在做修改或者添加代码的位置做了中文注释。
utils.js——修改1处
'use strict'const path= require('path')
const config= require('../config')
const ExtractTextPlugin= require('extract-text-webpack-plugin')
const packageConfig= require('../package.json')
exports.assetsPath= function(_path) {
const assetsSubDirectory= process.env.NODE_ENV === 'production'
?config.build.assetsSubDirectory
: config.dev.assetsSubDirectoryreturnpath.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders= function(options) {
options= options ||{}
const cssLoader={
loader:'css-loader',
options: {
minimize: process.env.NODE_ENV=== 'production',
sourceMap: options.sourceMap
}
}
const postcssLoader={
loader:'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}//generate loader string to be used with extract text plugin
functiongenerateLoaders (loader, loaderOptions) {
const loaders= options.usePostCSS ?[cssLoader, postcssLoader] : [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) {returnExtractTextPlugin.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) {
const output=[]
const loaders=exports.cssLoaders(options)for (const extension inloaders) {
const loader=loaders[extension]
output.push({
test:new RegExp('\\.' + extension + '$'),
use: loader
})
}returnoutput
}/*这里是添加的部分 ---------------------------- 开始*/
//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
})returnmap
}//多页面输出配置//与上面的多页面入口配置相同,读取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(newHtmlWebpackPlugin(conf))
})returnarr
}/*这里是添加的部分 ---------------------------- 结束*/exports.createNotifierCallback= () =>{
const notifier= require('node-notifier')return (severity, errors) =>{if (severity !== 'error') returnconst error= errors[0]
const filename= error.file && error.file.split('!').pop()
notifier.notify({
title: packageConfig.name,
message: severity+ ': ' +error.name,
subtitle: filename|| '',
icon: path.join(__dirname,'logo.png')
})
}
}
webpack.base.conf.js——修改1处
'use strict'const path= require('path')
const utils= require('./utils')
const config= require('../config')
const vueLoaderConfig= require('./vue-loader.conf')functionresolve (dir) {return path.join(__dirname, '..', dir)
}
module.exports={
context: path.resolve(__dirname,'../'),/*修改部分*/entry:utils.entries(),/*修改部分end*/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'),'@comp': resolve('src/components'),'@static': resolve('static'),
}
},
module: {
rules: [
{
test:/\.vue$/,
loader:'vue-loader',
options: vueLoaderConfig
},
{
test:/\.js$/,
loader:'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
{
test:/\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader:'url-loader',
options: {
limit:10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test:/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader:'url-loader',
options: {
limit:10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test:/\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader:'url-loader',
options: {
limit:10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
},
node: {//prevent webpack from injecting useless setImmediate polyfill because Vue
//source contains it (although only uses it if it's native).
setImmediate: false,//prevent webpack from injecting mocks to Node native modules
//that does not make sense for the client
dgram: 'empty',
fs:'empty',
net:'empty',
tls:'empty',
child_process:'empty'}
}
webpack.dev.conf.js——修改2处
'use strict'const utils= require('./utils')
const webpack= require('webpack')
const config= require('../config')
const merge= require('webpack-merge')
const path= require('path')
const baseWebpackConfig= require('./webpack.base.conf')
const CopyWebpackPlugin= require('copy-webpack-plugin')
const HtmlWebpackPlugin= require('html-webpack-plugin')
const FriendlyErrorsPlugin= require('friendly-errors-webpack-plugin')
const portfinder= require('portfinder')
const HOST=process.env.HOST
const PORT= process.env.PORT &&Number(process.env.PORT)
const devWebpackConfig=merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS:true})
},//cheap-module-eval-source-map is faster for development
devtool: config.dev.devtool,//these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel:'warning',
historyApiFallback: {
rewrites: [
{ from:/.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
],
},
hot:true,
contentBase:false, //since we use CopyWebpackPlugin.
compress: true,
host: HOST||config.dev.host,
port: PORT||config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay? { warnings: false, errors: true}
:false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet:true, //necessary for FriendlyErrorsPlugin
watchOptions: {
poll: config.dev.poll,
}
},
plugins: [newwebpack.DefinePlugin({'process.env': require('../config/dev.env')
}),newwebpack.HotModuleReplacementPlugin(),new webpack.NamedModulesPlugin(), //HMR shows correct file names in console on update.
newwebpack.NoEmitOnErrorsPlugin(),//https://github.com/ampedandwired/html-webpack-plugin
/*注释掉的文件*/
//new HtmlWebpackPlugin({//filename: 'index.html',//template: 'index.html',//inject: true//}),
/*注释掉的文件end*/
//copy custom static assets
newCopyWebpackPlugin([
{
from: path.resolve(__dirname,'../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
])
].concat(utils.htmlPlugin())//追加的代码
})
module.exports= new Promise((resolve, reject) =>{
portfinder.basePort= process.env.PORT ||config.dev.port
portfinder.getPort((err, port)=>{if(err) {
reject(err)
}else{//publish the new Port, necessary for e2e tests
process.env.PORT =port//add port to devServer config
devWebpackConfig.devServer.port =port//Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(newFriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors?utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})
webpack.prod.conf.js——修改2处
'use strict'const path= require('path')
const utils= require('./utils')
const webpack= require('webpack')
const config= require('../config')
const merge= require('webpack-merge')
const baseWebpackConfig= require('./webpack.base.conf')
const CopyWebpackPlugin= require('copy-webpack-plugin')
const HtmlWebpackPlugin= require('html-webpack-plugin')
const ExtractTextPlugin= require('extract-text-webpack-plugin')
const OptimizeCSSPlugin= require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin= require('uglifyjs-webpack-plugin')
const env= require('../config/prod.env')
const webpackConfig=merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract:true,
usePostCSS:true})
},
devtool: config.build.productionSourceMap? config.build.devtool : 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
newwebpack.DefinePlugin({'process.env': env
}),newUglifyJsPlugin({
uglifyOptions: {
compress: {
warnings:false}
},
sourceMap: config.build.productionSourceMap,
parallel:true}),//extract css into its own file
newExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),//Setting the following option to `false` will not extract CSS from codesplit chunks.
//Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
//It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
//increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
allChunks: true,
}),//Compress extracted CSS. We are using this plugin so that possible
//duplicated CSS from different components can be deduped.
newOptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap? { safe: true, map: { inline: false} }
: { 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'//}),
/*注释的代码end*/
//keep module.id stable when vendor modules does not change
newwebpack.HashedModuleIdsPlugin(),//enable scope hoisting
newwebpack.optimize.ModuleConcatenationPlugin(),//split vendor js into its own file
newwebpack.optimize.CommonsChunkPlugin({
name:'vendor',
minChunks (module) {//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
newwebpack.optimize.CommonsChunkPlugin({
name:'manifest',
minChunks: Infinity
}),//This instance extracts shared chunks from code splitted chunks and bundles them
//in a separate chunk, similar to the vendor chunk
//see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
newwebpack.optimize.CommonsChunkPlugin({
name:'app',
async:'vendor-async',
children:true,
minChunks:3}),//copy custom static assets
newCopyWebpackPlugin([
{
from: path.resolve(__dirname,'../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
].concat(utils.htmlPlugin())//追加的代码
})if(config.build.productionGzip) {
const CompressionWebpackPlugin= require('compression-webpack-plugin')
webpackConfig.plugins.push(newCompressionWebpackPlugin({
asset:'[path].gz[query]',
algorithm:'gzip',
test:newRegExp('\\.(' +config.build.productionGzipExtensions.join('|') +
')$'),
threshold:10240,
minRatio:0.8})
)
}if(config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin= require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(newBundleAnalyzerPlugin())
}
module.exports= webpackConfig
3. 页面改造:
1)src目录下新建pages文件夹,用来存放页面。
2)新建index文件夹作为主页(也可以定义为home).
3)将src目录下的App.vue和main.js以及根目录下的index.html文件统统放到index文件夹中,并将main.js改为index.js(html文件即为打包好直接访问的页面模板,决定了访问的文件路径,最好跟页面文件夹一致,js文件为页面入口,需要与html文件一致;vue文件名不限制,只需要在js文件中正确引入就可以了)。
一个页面文件夹中文件的格式分别如下:
App.vue
login