用vue构建多页面应用的示例代码
最近一直在研究使用vue做出来一些东西,但都是spa的单页面应用,但实际工作中,单页面并不一定符合业务需求,所以这篇我就来说说怎么开发多页面的vue应用,以及在这个过程会遇到的问题。
这是我放在github上的项目,里面有整个配置文件,可以参看一下:multiple-vue-page
准备工作
在本地用vue-cli新建一个项目,这个步骤vue的官网上有,我就不再说了。
这里有一个地方需要改一下,在执行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后缀文件,然后放入数组中 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 /* 注释这个区域的文件 ------------- 开始 */ // new htmlwebpackplugin({ // filename: 'index.html', // template: 'index.html', // inject: true // }), /* 注释这个区域的文件 ------------- 结束 */ 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 /* 注释这个区域的内容 ---------------------- 开始 */ // 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' // }), /* 注释这个区域的内容 ---------------------- 结束 */ // 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的配置就结束了。
但是还没完啦,下面继续。
文件结构
├── 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分别是静态资源文件、组件文件、页面文件。
前两个就不多说,主要是页面文件里,我目前是按照项目的模块分的文件夹,你也可以按照你自己的需求调整。然后在每个模块里又有三个内容:vue文件,js文件和html文件。这三个文件的作用就相当于做spa单页面应用时,根目录的index.html页面模板,src文件下的main.js和app.vue的功能。
原先,入口文件只有一个main.js,但现在由于是多页面,因此入口页面多了,我目前就是两个:index和cell,之后如果打包,就会在dist文件下生成两个html文件:index.html和cell.html(可以参考一下单页面应用时,打包只会生成一个index.html,区别在这里)。
cell文件下的三个文件,就是一般模式的配置,参考index的就可以,但并不完全相同。
特别注意的地方
cell.js
在这个文件里,按照写法,应该是这样的吧:
import vue from 'vue' import cell from './cell.vue' new vue({ el:'#app',// 这里参考cell.html和cell.vue的根节点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>)
网上的解释是这样的:
运行时构建不包含模板编译器,因此不支持 template 选项,只能用 render 选项,但即使使用运行时构建,在单文件组件中也依然可以写模板,因为单文件组件的模板会在构建时预编译为 render 函数。运行时构建比独立构建要轻量30%,只有 17.14 kb min+gzip大小。
上面一段是官方api中的解释。就是说,如果我们想使用template,我们不能直接在客户端使用npm install之后的vue。
也给出了相应的修改方案:
resolve: { alias: { 'vue': 'vue/dist/vue.js' } }
这里是修改package.json的resolve下的vue的配置,很多人反应这样修改之后就好了,但是我按照这个方法修改之后依然报错。然后我就想到上面提到的render函数,因此我的修改是针对cell.js文件的。
import vue from 'vue' import cell from './cell.vue' /* eslint-disable no-new */ new vue({ el: '#app', render: h => h(cell) })
这里面我用render函数取代了组件的写法,在运行就没问题了。
页面跳转
既然是多页面,肯定涉及页面之间的互相跳转,就按照我这个项目举例,从index.html文件点击a标签跳转到cell.html。
我最开始写的是:
<!-- index.html --> <a href='../cell/cell.html'></a>
但这样写,不论是在开发环境还是最后测试,都会报404,找不到这个页面。
改成这样既可:
<!-- index.html --> <a href='cell.html'></a>
这样他就会自己找cell.html这个文件。
打包后的资源路径
执行npm run build之后,打开相应的html文件你是看不到任何东西的,查看原因是找不到相应的js文件和css文件。
这时候的文件结构是这样的:
├── dist │ ├── js │ ├── css │ ├── index.html │ └── cell.html
查看index.html文件之后会发现资源的引用路径是:
/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看看吧。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。