手把手教你vue-cli单页到多页应用的方法


Posted in Javascript onMay 31, 2018

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 代表多页 每页单独一个文件夹 文件夹下建对应文件

手把手教你vue-cli单页到多页应用的方法

最后,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'),

手把手教你vue-cli单页到多页应用的方法

顺便把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

手把手教你vue-cli单页到多页应用的方法

手把手教你vue-cli单页到多页应用的方法

总结:核心点就在创建并配置私服和修改出口入口配置,坑就在版本不兼容

建议:cli一个vue的demo项目 从头撸一遍 再在实际项目里使用,而不是copy一下运行没问题搞定~

建议而已,你怎么打人,呜呜呜~

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
在Z-Blog中运行代码[html][/html](纯JS版)
Mar 25 Javascript
CSS JavaScript 实现菜单功能 改进版
Dec 09 Javascript
JQuery 将元素显示在屏幕的中央的代码
Feb 27 Javascript
Jquery插件写法笔记整理
Sep 06 Javascript
jquery append 动态添加的元素事件on 不起作用的解决方案
Jul 30 Javascript
设计模式中的facade外观模式在JavaScript开发中的运用
May 18 Javascript
javascript 正则表达式去空行方法
Jan 24 Javascript
ES6新特性一: let和const命令详解
Apr 20 Javascript
vue动态路由配置及路由传参的方式
May 23 Javascript
Mint UI实现A-Z字母排序的城市选择列表
Dec 28 Javascript
JavaScript在web自动化测试中的作用示例详解
Aug 25 Javascript
解决使用layui对select append元素无效或者未及时更新的问题
Sep 18 Javascript
详解基于Node.js的HTTP/2 Server实践
May 31 #Javascript
jQuery基于Ajax实现读取XML数据功能示例
May 31 #jQuery
jQuery实现动态加载select下拉列表项功能示例
May 31 #jQuery
vuejs实现标签选项卡动态更改css样式的方法
May 31 #Javascript
Vue组件之极简的地址选择器的实现
May 31 #Javascript
node跨域转发 express+http-proxy-middleware的使用
May 31 #Javascript
Vue实现侧边菜单栏手风琴效果实例代码
May 31 #Javascript
You might like
PHP 获取远程文件大小的3种解决方法
2013/07/11 PHP
注释PHP和html混合代码的小技巧(分享)
2016/11/03 PHP
Laravel Eloquent分表方法并使用模型关联的实现
2019/11/25 PHP
Javascript和Ajax中文乱码吐血版解决方案
2009/12/21 Javascript
JavaScript?Apple设备检测示例代码
2013/11/15 Javascript
AngularJS中实现显示或隐藏动画效果的方式总结
2015/12/31 Javascript
微信小程序开发之录音机 音频播放 动画实例 (真机可用)
2016/12/08 Javascript
js自制图片放大镜功能
2017/01/24 Javascript
Vue实现动态显示textarea剩余字数
2017/05/22 Javascript
NodeJs入门教程之定时器和队列
2019/03/08 NodeJs
jquery实现垂直无限轮播的方法分析
2019/07/16 jQuery
原生js实现下拉选项卡
2019/11/27 Javascript
JavaScript中的this原理及6种常见使用场景详解
2020/02/14 Javascript
vue渲染方式render和template的区别
2020/06/05 Javascript
实例讲解React 组件
2020/07/07 Javascript
.netcore+vue 实现压缩文件下载功能
2020/09/24 Javascript
Python  pip安装lxml出错的问题解决办法
2017/02/10 Python
Python更新数据库脚本两种方法及对比介绍
2017/07/27 Python
Python实现快速傅里叶变换的方法(FFT)
2018/07/21 Python
Python实现输入二叉树的先序和中序遍历,再输出后序遍历操作示例
2018/07/27 Python
python爬取指定微信公众号文章
2018/12/20 Python
用Pycharm实现鼠标滚轮控制字体大小的方法
2019/01/15 Python
python实现截取屏幕保存文件,删除N天前截图的例子
2019/08/27 Python
python代码打印100-999之间的回文数示例
2019/11/24 Python
python上传时包含boundary时的解决方法
2020/04/08 Python
利用Python发送邮件或发带附件的邮件
2020/11/12 Python
使用bandit对目标python代码进行安全函数扫描的案例分析
2021/01/27 Python
HTML5-WebSocket实现聊天室示例
2016/12/15 HTML / CSS
意大利领先的线上奢侈品销售电商:Eleonora Bonucci
2017/10/17 全球购物
高分子材料与工程专业推荐信
2013/12/01 职场文书
优秀三好学生事迹材料
2014/08/31 职场文书
《微笑着面对生活》优秀演讲稿范文
2014/09/23 职场文书
2015年街道除四害工作总结
2015/05/15 职场文书
基督教追悼会答谢词
2015/09/29 职场文书
2016年三八红旗手先进事迹材料
2016/02/26 职场文书
Redis如何使用乐观锁(CAS)保证数据一致性
2022/03/25 Redis