资讯专栏INFORMATION COLUMN

【敲黑板】手把手教你vue-cli单页到多页应用

DC_er / 1609人阅读

摘要:到多页应用前言我有一个创建的项目,但是我想做成多页应用,怎么办,废话不多说,直接开撸约定新增代码部分在和中间删除注释代码部分在和中间,很多东西都写在注释里第一步一个项目新建一个项目官网默认使用的服务,这个服务是做不了单页的,需要手动建一

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

</>复制代码

  1. "use strict"
  2. const path = require("path")
  3. const config = require("../config")
  4. const ExtractTextPlugin = require("extract-text-webpack-plugin")
  5. const packageConfig = require("../package.json")
  6. //add
  7. const glob = require("glob");
  8. const HtmlWebpackPlugin = require("html-webpack-plugin"); //功能:生成html文件及js文件并把js引入html
  9. const pagePath = path.resolve(__dirname, "../src/views/"); //页面的路径,比如这里我用的views,那么后面私服加入的文件监控器就会从src下面的views下面开始监控文件
  10. //end
  11. exports.assetsPath = function (_path) {
  12. const assetsSubDirectory = process.env.NODE_ENV === "production"
  13. ? config.build.assetsSubDirectory
  14. : config.dev.assetsSubDirectory
  15. return path.posix.join(assetsSubDirectory, _path)
  16. }
  17. exports.cssLoaders = function (options) {
  18. options = options || {}
  19. const cssLoader = {
  20. loader: "css-loader",
  21. options: {
  22. sourceMap: options.sourceMap
  23. }
  24. }
  25. const postcssLoader = {
  26. loader: "postcss-loader",
  27. options: {
  28. sourceMap: options.sourceMap
  29. }
  30. }
  31. // generate loader string to be used with extract text plugin
  32. function generateLoaders (loader, loaderOptions) {
  33. const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
  34. if (loader) {
  35. loaders.push({
  36. loader: loader + "-loader",
  37. options: Object.assign({}, loaderOptions, {
  38. sourceMap: options.sourceMap
  39. })
  40. })
  41. }
  42. // Extract CSS when that option is specified
  43. // (which is the case during production build)
  44. if (options.extract) {
  45. return ExtractTextPlugin.extract({
  46. use: loaders,
  47. fallback: "vue-style-loader"
  48. })
  49. } else {
  50. return ["vue-style-loader"].concat(loaders)
  51. }
  52. }
  53. // https://vue-loader.vuejs.org/en/configurations/extract-css.html
  54. return {
  55. css: generateLoaders(),
  56. postcss: generateLoaders(),
  57. less: generateLoaders("less"),
  58. sass: generateLoaders("sass", { indentedSyntax: true }),
  59. scss: generateLoaders("sass"),
  60. stylus: generateLoaders("stylus"),
  61. styl: generateLoaders("stylus")
  62. }
  63. }
  64. // Generate loaders for standalone style files (outside of .vue)
  65. exports.styleLoaders = function (options) {
  66. const output = []
  67. const loaders = exports.cssLoaders(options)
  68. for (const extension in loaders) {
  69. const loader = loaders[extension]
  70. output.push({
  71. test: new RegExp("." + extension + "$"),
  72. use: loader
  73. })
  74. }
  75. return output
  76. }
  77. exports.createNotifierCallback = () => {
  78. const notifier = require("node-notifier")
  79. return (severity, errors) => {
  80. if (severity !== "error") return
  81. const error = errors[0]
  82. const filename = error.file && error.file.split("!").pop()
  83. notifier.notify({
  84. title: packageConfig.name,
  85. message: severity + ": " + error.name,
  86. subtitle: filename || "",
  87. icon: path.join(__dirname, "logo.png")
  88. })
  89. }
  90. }
  91. //add 新增一个方法处理入口文件(单页应用的入口都是写死,到时候替换成这个方法)
  92. exports.createEntry = () => {
  93. let files = glob.sync(pagePath + "/**/*.js");
  94. let entries = {};
  95. let basename;
  96. let foldername;
  97. files.forEach(entry => {
  98. // Filter the router.js
  99. basename = path.basename(entry, path.extname(entry), "router.js");
  100. foldername = path.dirname(entry).split("/").splice(-1)[0];
  101. // If foldername not equal basename, doing nothing
  102. // The folder maybe contain more js files, but only the same name is main
  103. if (basename === foldername) {
  104. entries[basename] = process.env.NODE_ENV === "development" ?
  105. [
  106. "webpack-hot-middleware/client?noInfo=true&reload=true&path=/__webpack_hmr&timeout=20000",
  107. entry
  108. ]: [entry];
  109. }
  110. });
  111. return entries;
  112. };
  113. //end
  114. //add 新增出口文件
  115. exports.createHtmlWebpackPlugin = (publicModule) => {
  116. let files = glob.sync(pagePath + "/**/*.html", {matchBase: true});
  117. let entries = exports.createEntry();
  118. let plugins = [];
  119. let conf;
  120. let basename;
  121. let foldername;
  122. publicModule = publicModule || [];
  123. files.forEach(file => {
  124. basename = path.basename(file, path.extname(file));
  125. foldername = path.dirname(file).split("/").splice(-1).join("");
  126. if (basename === foldername) {
  127. conf = {
  128. template: file,
  129. filename: basename + ".html",
  130. inject: true,
  131. chunks: entries[basename] ? [basename] : []
  132. };
  133. if (process.env.NODE_ENV !== "development") {
  134. conf.chunksSortMode = "dependency";
  135. conf.minify = {
  136. removeComments: true,
  137. collapseWhitespace: true,
  138. removeAttributeQuotes: true
  139. };
  140. // 在构建生产环境时,需要指定共用模块
  141. conf.chunks = [...publicModule, ...conf.chunks];
  142. }
  143. plugins.push(new HtmlWebpackPlugin(conf));
  144. }
  145. });
  146. return plugins;
  147. };
  148. //end
第三步:创建私服(不使用dev-server服务,自己建一个)

从express新建私服并配置(build文件夹下新建 我这里叫webpack.dev.client.js)

webpack.dev.client.js

</>复制代码

  1. /**
  2. * created by qbyu2 on 2018-05-30
  3. * express 私服
  4. * */
  5. "use strict";
  6. const fs = require("fs");
  7. const path = require("path");
  8. const express = require("express");
  9. const webpack = require("webpack");
  10. const webpackDevMiddleware = require("webpack-dev-middleware"); //文件监控(前面配置了从views下面监控)
  11. const webpackHotMiddleware = require("webpack-hot-middleware"); //热加载
  12. const config = require("../config");
  13. const devWebpackConfig = require("./webpack.dev.conf");
  14. const proxyMiddleware = require("http-proxy-middleware"); //跨域
  15. const proxyTable = config.dev.proxyTable;
  16. const PORT = config.dev.port;
  17. const HOST = config.dev.host;
  18. const assetsRoot = config.dev.assetsRoot;
  19. const app = express();
  20. const router = express.Router();
  21. const compiler = webpack(devWebpackConfig);
  22. let devMiddleware = webpackDevMiddleware(compiler, {
  23. publicPath: devWebpackConfig.output.publicPath,
  24. quiet: true,
  25. stats: {
  26. colors: true,
  27. chunks: false
  28. }
  29. });
  30. let hotMiddleware = webpackHotMiddleware(compiler, {
  31. path: "/__webpack_hmr",
  32. heartbeat: 2000
  33. });
  34. app.use(hotMiddleware);
  35. app.use(devMiddleware);
  36. Object.keys(proxyTable).forEach(function (context) {
  37. let options = proxyTable[context];
  38. if (typeof options === "string") {
  39. options = {
  40. target: options
  41. };
  42. }
  43. app.use(proxyMiddleware(context, options));
  44. });
  45. //双路由 私服一层控制私服路由 vue的路由控制该页面下的路由
  46. app.use(router)
  47. app.use("/static", express.static(path.join(assetsRoot, "static")));
  48. let sendFile = (viewname, response, next) => {
  49. compiler.outputFileSystem.readFile(viewname, (err, result) => {
  50. if (err) {
  51. return (next(err));
  52. }
  53. response.set("content-type", "text/html");
  54. response.send(result);
  55. response.end();
  56. });
  57. };
  58. //拼接方法
  59. function pathJoin(patz) {
  60. return path.join(assetsRoot, patz);
  61. }
  62. /**
  63. * 定义路由(私服路由 非vue路由)
  64. * */
  65. // favicon
  66. router.get("/favicon.ico", (req, res, next) => {
  67. res.end();
  68. });
  69. // http://localhost:8080/
  70. router.get("/", (req, res, next)=>{
  71. sendFile(pathJoin("index.html"), res, next);
  72. });
  73. // http://localhost:8080/home
  74. router.get("/:home", (req, res, next) => {
  75. sendFile(pathJoin(req.params.home + ".html"), res, next);
  76. });
  77. // http://localhost:8080/index
  78. router.get("/:index", (req, res, next) => {
  79. sendFile(pathJoin(req.params.index + ".html"), res, next);
  80. });
  81. module.exports = app.listen(PORT, err => {
  82. if (err){
  83. return
  84. }
  85. console.log(`Listening at http://${HOST}:${PORT}
  86. `);
  87. })

私服创建好了 安装下依赖
有坑。。。
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

</>复制代码

  1. 把原来写死的
  2. entry: {
  3. app: "./src/index.js"
  4. },
  5. 改为:
  6. entry: utils.createEntry(),

webpack.dev.conf.js

</>复制代码

  1. "use strict"
  2. const utils = require("./utils")
  3. const webpack = require("webpack")
  4. const config = require("../config")
  5. const merge = require("webpack-merge")
  6. const path = require("path")
  7. const baseWebpackConfig = require("./webpack.base.conf")
  8. const CopyWebpackPlugin = require("copy-webpack-plugin")
  9. const HtmlWebpackPlugin = require("html-webpack-plugin")
  10. const FriendlyErrorsPlugin = require("friendly-errors-webpack-plugin")
  11. const portfinder = require("portfinder")
  12. process.env.NODE_ENV = "development";
  13. const HOST = process.env.HOST
  14. const PORT = process.env.PORT && Number(process.env.PORT)
  15. const devWebpackConfig = merge(baseWebpackConfig, {
  16. module: {
  17. rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
  18. },
  19. // cheap-module-eval-source-map is faster for development
  20. devtool: config.dev.devtool,
  21. // these devServer options should be customized in /config/index.js
  22. //del 注掉SPA的服务器
  23. // devServer: {
  24. // clientLogLevel: "warning",
  25. // historyApiFallback: {
  26. // rewrites: [
  27. // { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, "index.html") },
  28. // ],
  29. // },
  30. // hot: true,
  31. // contentBase: false, // since we use CopyWebpackPlugin.
  32. // compress: true,
  33. // host: HOST || config.dev.host,
  34. // port: PORT || config.dev.port,
  35. // open: config.dev.autoOpenBrowser,
  36. // overlay: config.dev.errorOverlay
  37. // ? { warnings: false, errors: true }
  38. // : false,
  39. // publicPath: config.dev.assetsPublicPath,
  40. // proxy: config.dev.proxyTable,
  41. // quiet: true, // necessary for FriendlyErrorsPlugin
  42. // watchOptions: {
  43. // poll: config.dev.poll,
  44. // }
  45. // },
  46. //end
  47. plugins: [
  48. new webpack.DefinePlugin({
  49. "process.env": require("../config/dev.env")
  50. }),
  51. new webpack.HotModuleReplacementPlugin(),
  52. new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
  53. new webpack.NoEmitOnErrorsPlugin(),
  54. // https://github.com/ampedandwired/html-webpack-plugin
  55. //del 注释掉spa固定的单页出口 末尾动态配上出口
  56. // new HtmlWebpackPlugin({
  57. // filename: "index.html",
  58. // template: "index.html",
  59. // inject: true
  60. // }),
  61. //end
  62. // copy custom static assets
  63. new CopyWebpackPlugin([
  64. {
  65. from: path.resolve(__dirname, "../static"),
  66. to: config.dev.assetsSubDirectory,
  67. ignore: [".*"]
  68. }
  69. ])
  70. ]
  71. //add
  72. .concat(utils.createHtmlWebpackPlugin())
  73. //end
  74. })
  75. //del
  76. // module.exports = new Promise((resolve, reject) => {
  77. // portfinder.basePort = process.env.PORT || config.dev.port
  78. // portfinder.getPort((err, port) => {
  79. // if (err) {
  80. // reject(err)
  81. // } else {
  82. // // publish the new Port, necessary for e2e tests
  83. // process.env.PORT = port
  84. // // add port to devServer config
  85. // devWebpackConfig.devServer.port = port
  86. //
  87. // // Add FriendlyErrorsPlugin
  88. // devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
  89. // compilationSuccessInfo: {
  90. // messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
  91. // },
  92. // onErrors: config.dev.notifyOnErrors
  93. // ? utils.createNotifierCallback()
  94. // : undefined
  95. // }))
  96. //
  97. // resolve(devWebpackConfig)
  98. // }
  99. // })
  100. // })
  101. //end
  102. module.exports = devWebpackConfig;

webpack.prod.conf.js

plugins最后加上.concat(utils.createHtmlWebpackPlugin(["manifest", "vendor"]))
test环境一样

第五步:修改package.json 指令配置

scripts下面"dev":
这样执行的时候就不会走默认的dev-server而走你的私服了

</>复制代码

  1. "scripts": {
  2. "dev": "node build/webpack.dev.client.js",
  3. "start": "npm run dev",
  4. "build": "node build/build.js"
  5. },
第六步:创建测试文件

src目录下新建 views文件夹 (代码注释里有 当时配的目录跟这个一致就可以 随便你命名 遵循命名规范就行)
views 文件夹下新建两个文件夹index和home 代表多页 每页多带带一个文件夹 文件夹下建对应文件

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

</>复制代码

  1. assetsPublicPath: "/", => assetsPublicPath: "./",

最后,npm run dev 或者 npm run build

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

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

1.webpack.dev.client.js

</>复制代码

  1. //双路由 私服一层控制私服路由 vue的路由控制该页面下的路由
  2. app.use(router)
  3. app.use("/static", express.static(path.join(assetsRoot, "static")));

这个assetsRoot cli创建的时候是没有的 在config/index.js 下面找到dev加上

</>复制代码

  1. assetsRoot: path.resolve(__dirname, "../dist"),

2.还是版本问题

webpack-dev-middleware 默认是3.1.3版本但是会报错
具体哪个版本不报错我也不知道

</>复制代码

  1. context.compiler.hooks.invalid.tap("WebpackDevMiddleware", invalid);

找不到invalid 源码里面是有的
卸载webpack-dev-middleware

</>复制代码

  1. npm uninstall webpack-dev-middleware

使用dev-server自带的webpack-dev-middleware (cli单页应用是有热加载的)
重新install dev-server

</>复制代码

  1. npm install webpack-dev-server@2.10.0 --save-dev

</>复制代码

  1. npm run dev

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

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

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

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

build 后 没有引入共用模块

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

代码仓库:github

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107860.html

相关文章

  • 前方来报,八月最新资讯--关于vue2&3的最佳文章推荐

    摘要:哪吒别人的看法都是狗屁,你是谁只有你自己说了才算,这是爹教我的道理。哪吒去他个鸟命我命由我,不由天是魔是仙,我自己决定哪吒白白搭上一条人命,你傻不傻敖丙不傻谁和你做朋友太乙真人人是否能够改变命运,我不晓得。我只晓得,不认命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出处 查看github最新的Vue...

    izhuhaodev 评论0 收藏0
  • 架构 - 收藏集 - 掘金

    摘要:浅谈秒杀系统架构设计后端掘金秒杀是电子商务网站常见的一种营销手段。这两个项目白话网站架构演进后端掘金这是白话系列的文章。 浅谈秒杀系统架构设计 - 后端 - 掘金秒杀是电子商务网站常见的一种营销手段。 不要整个系统宕机。 即使系统故障,也不要将错误数据展示出来。 尽量保持公平公正。 实现效果 秒杀开始前,抢购按钮为活动未开始。 秒杀开始时,抢购按钮可以点击下单。 秒杀结束后,按钮按钮变...

    Riddler 评论0 收藏0
  • vue单页多页的开发环境配置+vue的开发思路

    摘要:多个单页应用整合的工程的开发环境工程的目录设置本文内容的工程的目录设计基于的多个单页应用的开发环境搭建目录一开发环境使用二需求分析三开发思路四目录设计及思路五开发环境开发六整个开发环境的目录注释一开发环境使用多终端页面路径设置 vue-multi-device-single-page 多个单页应用整合的vue工程的开发环境vue工程的目录设置 showImg(https://segme...

    cnio 评论0 收藏0
  • 基于webpack4的VUE多页脚手架

    摘要:另外备注一部分参数的说明折叠有助于文档树中文本节点的空白区域对进行压缩默认默认按照不同文件的依赖关系来排序。敲黑板讲重点的当然目前这部分的文档在官网还不是很全,所以这里我们参考了印记中文的说明文档,指优化模块。 链接 写在前面 为什么要自己手写一个脚手架? 如何去思考遇到的问题? 正文 链接 原文链接 github whale-vue ——写在前面 1、为什么要自己手写...

    张金宝 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<