44

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] = process.env.NODE_ENV === 'development' ?
        [
          'webpack-hot-middleware/client?noInfo=true&reload=true&path=/__webpack_hmr&timeout=20000',
          entry
        ]: [entry];
    }
  });
  return entries;
};
//end

//add 新增出口文件
exports.createHtmlWebpackPlugin = (publicModule) => {
  let files = glob.sync(pagePath + '/**/*.html', {matchBase: true});
  let entries = exports.createEntry();
  let plugins = [];
  let conf;
  let basename;
  let foldername;
  publicModule = publicModule || [];

  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
        };
        // 在构建生产环境时,需要指定共用模块
        conf.chunks = [...publicModule, ...conf.chunks];
      }

      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
把原来写死的
entry: {
    app: './src/index.js'
  },
改为:
entry: utils.createEntry(),
  • 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')

process.env.NODE_ENV = 'development';

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(['manifest', 'vendor']))
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 代表多页 每页单独一个文件夹 文件夹下建对应文件

打包改为相对路径config/index.js
build下面

assetsPublicPath: '/',   =>   assetsPublicPath: './',

clipboard.png

最后,npm run dev 或者 npm run build

测试环境自己配 跟 生产环境差不多,就几个配置参数不一样

这个时候你会发现,特么的什么鬼文章 报错了啊
稍安勿躁~
两个地方,

  • 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'),

clipboard.png

  • 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

clipboard.png

clipboard.png

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

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

快过节了,觉得本文对你有用的话请随意打赏,让作者可以买个棒棒糖吃~

-------------------------------------------6.1更-----------------------------------------
留了一个坑,一天了,有赞有收藏,没见人评论指出坑,心痛的无法呼吸~

build 后 没有引入共用模块

代码已更新~ build后可正常访问...

代码仓库:github

注:内容有不当或者错误处请指正~转载请注明出处~谢谢合作!


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

41 条评论
绝恋 · 2018年05月31日

记笔记记笔记

回复

0

皮这一下很开心

吃葡萄不吐番茄皮 作者 · 2018年05月31日
ooort6 · 2018年05月31日

脚手架搭不起来怎么办。。。。。

回复

0

先装node,然后跟着官网教程来啊 装vue 初始化 或者找个文章看一下 装不了有可能是网不行,node很慢卡住或者node版本太低

吃葡萄不吐番茄皮 作者 · 2018年05月31日
ooort6 · 2018年05月31日

node 是10.2.0 然后就只有一步?project name obj 然后就不动了。。

回复

0

回车= =

吃葡萄不吐番茄皮 作者 · 2018年05月31日
0

问号是让你选或者填项目参数

吃葡萄不吐番茄皮 作者 · 2018年05月31日
0

我又不是傻子。。。。。。。。。。。。

ooort6 · 2018年05月31日
萌小屋 · 2018年05月31日
第三步:创建私服(不使用dev-server服务,自己建一个)
这里是解决了开发环境的什么问题?

回复

0

cli的dev-server服务不支持多页,只能自己实例化一个express

吃葡萄不吐番茄皮 作者 · 2018年05月31日
ZIFeng · 2018年06月01日

这样做可以使用vuex 和组件吗

回复

0

当然可以啊,动的是webpack的服务,没动vue哦,注意下双路由,有点绕要理解

吃葡萄不吐番茄皮 作者 · 2018年06月01日
初见ld · 2018年06月05日

源码demo 能发下吗,按这个来,我这边还是跑不通

回复

0

准备放github的,一直在忙,中午休息的时候放

吃葡萄不吐番茄皮 作者 · 2018年06月05日
0

赞!

初见ld · 2018年06月05日
0

@初见ld 好了 老铁,多谢提醒,注意下版本

吃葡萄不吐番茄皮 作者 · 2018年06月05日
会幸运的倒霉熊 · 2018年06月05日

第四步,修改配置:
1.修改webpack.base.conf.js 文件,
里面又有 require('./webpack.base.conf'),是不是错了呀。

回复

0

感谢老铁 粗心把那个文件代码copy错了 /捂脸

吃葡萄不吐番茄皮 作者 · 2018年06月05日
suyi91 · 2018年06月07日

楼主 webpackDevServer可以配置支持多入口页
devServer.historyApiFallback
https://webpack.js.org/config...

现在公司的项目约定src/entries中存放各个单页模块,webpack启动时通过glob获取所有SPA模块信息列表实现开发模式和生产模式的全套自动配置

回复

0

晚上看下 感谢老铁, 我也觉得先建个服务器很麻烦

吃葡萄不吐番茄皮 作者 · 2018年06月07日
小明明明明明白白 · 2018年06月08日

楼主 这个私服的作用是什么啊 (我是个很小的小白)

回复

lmy233 · 2018年06月13日

楼主大大,github里面的文档,npm run dev后报错,能再看下嘛

回复

0

@lmy233 带截图私信吧,版本对的吗

吃葡萄不吐番茄皮 作者 · 2018年06月13日
0

可以了,不好意思,感谢大大分享

lmy233 · 2018年06月14日
0

@lmy233 那就好

吃葡萄不吐番茄皮 作者 · 2018年06月14日
mypoly · 2018年06月15日

我想问一下,什么时候用单页,什么时候用多页?

回复

0

抱歉啊,放假没看,这个单页多页是根据需求来的,看你这几个页面之间有没有共用模块

吃葡萄不吐番茄皮 作者 · 2018年06月19日
Kathy丶Andy · 2018年08月07日

http 网络请求能封装全局的吗,每个页面都可以用的,每个模块里面的html 能写个模板吗

回复

0

@Kathy丶Andy 可以的,模版的话周四加在文章结尾,这几天都请假办事去了

吃葡萄不吐番茄皮 作者 · 2018年08月07日
0

期待你的好作品

Kathy丶Andy · 2018年08月08日
0
吃葡萄不吐番茄皮 作者 · 2018年08月09日
renleiabc · 2018年09月07日

为什么在mode: 'history',模式下,无法渲染出vue组件

回复

0

@renleiabc 路由找不到模块了,在path加项目名称

吃葡萄不吐番茄皮 作者 · 2018年09月07日
0

在哪个文件下添加path呢?

renleiabc · 2018年09月07日
0

@renleiabc 看你在哪配置路由里 一般在router/index.js

吃葡萄不吐番茄皮 作者 · 2018年09月07日
飞鸟 · 2018年12月11日

请问双路由怎么处理?

回复

飞鸟 · 2018年12月11日

Error: no such file or directory

at MemoryFileSystem.readFileSync (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\memory-fs\lib\MemoryFileSystem.js:107:10)
at MemoryFileSystem.(anonymous function) [as readFile] (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\memory-fs\lib\MemoryFileSystem.js:300:34)
at sendFile (C:\Users\15810\Desktop\vue-cli-multi-page-master\build\webpack.dev.client.js:58:29)
at router.get (C:\Users\15810\Desktop\vue-cli-multi-page-master\build\webpack.dev.client.js:89:3)
at Layer.handle [as handle_request] (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\express\lib\router\layer.js:95:5)
at next (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\express\lib\router\route.js:137:13)
at Route.dispatch (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\express\lib\router\route.js:112:3)
at Layer.handle [as handle_request] (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\express\lib\router\layer.js:95:5)
at C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\express\lib\router\index.js:281:22
at param (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\express\lib\router\index.js:354:14)
at param (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\express\lib\router\index.js:365:14)
at Function.process_params (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\express\lib\router\index.js:410:3)
at next (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\express\lib\router\index.js:275:10)
at Function.handle (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\express\lib\router\index.js:174:3)
at router (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\express\lib\router\index.js:47:12)
at Layer.handle [as handle_request] (C:\Users\15810\Desktop\vue-cli-multi-page-master\node_modules\express\lib\router\layer.js:95:5)

回复

载入中...