手把手教你vue-cli单页到多页应用的方法
vue-cli到多页应用
前言:我有一个cli创建的vue项目,但是我想做成多页应用,怎么办,废话不多说,直接开撸~
约定:新增代码部分在//add和//end中间 删除(注释)代码部分在//del和//end中间,很多东西都写在注释里
第一步:cli一个vue项目
新建一个vue项目 官网 vue init webpack demo
cli默认使用webpack的dev-server服务,这个服务是做不了单页的,需要手动建一个私服叫啥你随意 一般叫dev.server或者dev.client
第二步:添加两个方法处理出口入口文件(spa默认写死的)
进入刚刚创建vue项目 cd demo
在目录下面找到build/utils.js文件
修改部分:
utils.js
'use strict' const path = require('path') const config = require('../config') const extracttextplugin = require('extract-text-webpack-plugin') const packageconfig = require('../package.json') //add const glob = require('glob'); const htmlwebpackplugin = require('html-webpack-plugin'); //功能:生成html文件及js文件并把js引入html const pagepath = path.resolve(__dirname, '../src/views/'); //页面的路径,比如这里我用的views,那么后面私服加入的文件监控器就会从src下面的views下面开始监控文件 //end exports.assetspath = function (_path) { const assetssubdirectory = process.env.node_env === 'production' ? config.build.assetssubdirectory : config.dev.assetssubdirectory return path.posix.join(assetssubdirectory, _path) } exports.cssloaders = function (options) { options = options || {} const cssloader = { loader: 'css-loader', options: { sourcemap: options.sourcemap } } const postcssloader = { loader: 'postcss-loader', options: { sourcemap: options.sourcemap } } // generate loader string to be used with extract text plugin function generateloaders (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) { 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) { const output = [] const loaders = exports.cssloaders(options) for (const extension in loaders) { const loader = loaders[extension] output.push({ test: new regexp('\\.' + extension + '$'), use: loader }) } return output } exports.createnotifiercallback = () => { const notifier = require('node-notifier') return (severity, errors) => { if (severity !== 'error') return const 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') }) } } //add 新增一个方法处理入口文件(单页应用的入口都是写死,到时候替换成这个方法) exports.createentry = () => { let files = glob.sync(pagepath + '/**/*.js'); let entries = {}; let basename; let foldername; files.foreach(entry => { // filter the router.js basename = path.basename(entry, path.extname(entry), 'router.js'); foldername = path.dirname(entry).split('/').splice(-1)[0]; // if foldername not equal basename, doing nothing // the folder maybe contain more js files, but only the same name is main if (basename === foldername) { entries[basename] = [ 'webpack-hot-middleware/client?noinfo=true&reload=true&path=/__webpack_hmr&timeout=20000', entry]; } }); return entries; }; //end //add 新增出口文件 exports.createhtmlwebpackplugin = () => { let files = glob.sync(pagepath + '/**/*.html', {matchbase: true}); let entries = exports.createentry(); let plugins = []; let conf; let basename; let foldername; files.foreach(file => { basename = path.basename(file, path.extname(file)); foldername = path.dirname(file).split('/').splice(-1).join(''); if (basename === foldername) { conf = { template: file, filename: basename + '.html', inject: true, chunks: entries[basename] ? [basename] : [] }; if (process.env.node_env !== 'development') { conf.chunkssortmode = 'dependency'; conf.minify = { removecomments: true, collapsewhitespace: true, removeattributequotes: true }; } plugins.push(new htmlwebpackplugin(conf)); } }); return plugins; }; //end
第三步:创建私服(不使用dev-server服务,自己建一个)
从express新建私服并配置(build文件夹下新建 我这里叫webpack.dev.client.js)
webpack.dev.client.js
/** * created by qbyu2 on 2018-05-30 * express 私服 * */ 'use strict'; const fs = require('fs'); const path = require('path'); const express = require('express'); const webpack = require('webpack'); const webpackdevmiddleware = require('webpack-dev-middleware'); //文件监控(前面配置了从views下面监控) const webpackhotmiddleware = require('webpack-hot-middleware'); //热加载 const config = require('../config'); const devwebpackconfig = require('./webpack.dev.conf'); const proxymiddleware = require('http-proxy-middleware'); //跨域 const proxytable = config.dev.proxytable; const port = config.dev.port; const host = config.dev.host; const assetsroot = config.dev.assetsroot; const app = express(); const router = express.router(); const compiler = webpack(devwebpackconfig); let devmiddleware = webpackdevmiddleware(compiler, { publicpath: devwebpackconfig.output.publicpath, quiet: true, stats: { colors: true, chunks: false } }); let hotmiddleware = webpackhotmiddleware(compiler, { path: '/__webpack_hmr', heartbeat: 2000 }); app.use(hotmiddleware); app.use(devmiddleware); object.keys(proxytable).foreach(function (context) { let options = proxytable[context]; if (typeof options === 'string') { options = { target: options }; } app.use(proxymiddleware(context, options)); }); //双路由 私服一层控制私服路由 vue的路由控制该页面下的路由 app.use(router) app.use('/static', express.static(path.join(assetsroot, 'static'))); let sendfile = (viewname, response, next) => { compiler.outputfilesystem.readfile(viewname, (err, result) => { if (err) { return (next(err)); } response.set('content-type', 'text/html'); response.send(result); response.end(); }); }; //拼接方法 function pathjoin(patz) { return path.join(assetsroot, patz); } /** * 定义路由(私服路由 非vue路由) * */ // favicon router.get('/favicon.ico', (req, res, next) => { res.end(); }); // http://localhost:8080/ router.get('/', (req, res, next)=>{ sendfile(pathjoin('index.html'), res, next); }); // http://localhost:8080/home router.get('/:home', (req, res, next) => { sendfile(pathjoin(req.params.home + '.html'), res, next); }); // http://localhost:8080/index router.get('/:index', (req, res, next) => { sendfile(pathjoin(req.params.index + '.html'), res, next); }); module.exports = app.listen(port, err => { if (err){ return } console.log(`listening at http://${host}:${port}\n`); })
私服创建好了 安装下依赖
有坑。。。
webpack和热加载版本太高太低都不行
npm install webpack@3.10.0 --save-dev npm install webpack-dev-middleware --save-dev npm install webpack-hot-middleware@2.21.0 --save-dev npm install http-proxy-middleware --save-dev
第四步:修改配置webpack.base.conf.js
'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: [ new webpack.defineplugin({ 'process.env': require('../config/dev.env') }), new webpack.hotmodulereplacementplugin(), new webpack.namedmodulesplugin(), // hmr shows correct file names in console on update. new webpack.noemitonerrorsplugin(), // https://github.com/ampedandwired/html-webpack-plugin //del 注释掉spa固定的单页出口 末尾动态配上出口 // new htmlwebpackplugin({ // filename: 'index.html', // template: 'index.html', // inject: true // }), //end // copy custom static assets new copywebpackplugin([ { from: path.resolve(__dirname, '../static'), to: config.dev.assetssubdirectory, ignore: ['.*'] } ]) ] //add .concat(utils.createhtmlwebpackplugin()) //end }) //del // 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(new friendlyerrorsplugin({ // compilationsuccessinfo: { // messages: [`your application is running here: http://${devwebpackconfig.devserver.host}:${port}`], // }, // onerrors: config.dev.notifyonerrors // ? utils.createnotifiercallback() // : undefined // })) // // resolve(devwebpackconfig) // } // }) // }) //end
webpack.dev.conf.js
'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 //del 注掉spa的服务器 // 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, // } // }, //end plugins: [ new webpack.defineplugin({ 'process.env': require('../config/dev.env') }), new webpack.hotmodulereplacementplugin(), new webpack.namedmodulesplugin(), // hmr shows correct file names in console on update. new webpack.noemitonerrorsplugin(), // https://github.com/ampedandwired/html-webpack-plugin //del 注释掉spa固定的单页出口 末尾动态配上出口 // new htmlwebpackplugin({ // filename: 'index.html', // template: 'index.html', // inject: true // }), //end // copy custom static assets new copywebpackplugin([ { from: path.resolve(__dirname, '../static'), to: config.dev.assetssubdirectory, ignore: ['.*'] } ]) ] //add .concat(utils.createhtmlwebpackplugin()) //end }) //del // 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(new friendlyerrorsplugin({ // compilationsuccessinfo: { // messages: [`your application is running here: http://${devwebpackconfig.devserver.host}:${port}`], // }, // onerrors: config.dev.notifyonerrors // ? utils.createnotifiercallback() // : undefined // })) // // resolve(devwebpackconfig) // } // }) // }) //end module.exports = devwebpackconfig;
webpack.prod.conf.js
plugins最后加上.concat(utils.createhtmlwebpackplugin())
test环境一样
第五步:修改package.json 指令配置
scripts下面'dev':
这样执行的时候就不会走默认的dev-server而走你的私服了
"scripts": { "dev": "node build/webpack.dev.client.js", "start": "npm run dev", "build": "node build/build.js" },
第六步:创建测试文件
src目录下新建 views文件夹 (代码注释里有 当时配的目录跟这个一致就可以 随便你命名 遵循命名规范就行)
views 文件夹下新建两个文件夹index和home 代表多页 每页单独一个文件夹 文件夹下建对应文件
最后,npm run dev
这个时候你会发现,特么的什么鬼文章 报错了啊
稍安勿躁~
两个地方,
1.webpack.dev.client.js
//双路由 私服一层控制私服路由 vue的路由控制该页面下的路由 app.use(router) app.use('/static', express.static(path.join(assetsroot, 'static')));
这个assetsroot cli创建的时候是没有的 在config/index.js 下面找到dev加上
assetsroot: path.resolve(__dirname, '../dist'),
顺便把dev和build的assetspublicpath 路径都改成相对路径'./'
2.还是版本问题
webpack-dev-middleware 默认是3.1.3版本但是会报错
具体哪个版本不报错我也不知道
context.compiler.hooks.invalid.tap('webpackdevmiddleware', invalid);
找不到invalid 源码里面是有的
卸载webpack-dev-middleware
npm uninstall webpack-dev-middleware
使用dev-server自带的webpack-dev-middleware (cli单页应用是有热加载的)
重新install dev-server
npm install webpack-dev-server@2.10.0 --save-dev
npm run dev
总结:核心点就在创建并配置私服和修改出口入口配置,坑就在版本不兼容
建议:cli一个vue的demo项目 从头撸一遍 再在实际项目里使用,而不是copy一下运行没问题搞定~
建议而已,你怎么打人,呜呜呜~
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。