手把手教你 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/clientnoInfo=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:

'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

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.cssSourceMapusePostCSS: 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的服务器


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容