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')//addconst glob = require('glob');const HtmlWebpackPlugin = require('html-webpack-plugin'); //功能:生成html文件及js文件并把js引入htmlconst pagePath = path.resolve(__dirname, '../src/views/'); //页面的路径,比如这里我用的views,那么后面私服加入的文件监控器就会从src下面的views下面开始监控文件//endexports.assetsPath = function (_path) {const assetsSubDirectory = process.env.NODE_ENV === 'production'? config.build.assetsSubDirectory: config.dev.assetsSubDirectoryreturn 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 pluginfunction 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.htmlreturn {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') returnconst 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.jsbasename = 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 mainif (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)
/*** 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路由)* */// faviconrouter.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/homerouter.get('/:home', (req, res, next) => {sendFile(pathJoin(req.params.home + '.html'), res, next);});// http://localhost:8080/indexrouter.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 [email protected].10.0 --save-devnpm install webpack-dev-middleware --save-devnpm install webpack-hot-[email protected].21.0 --save-devnpm 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.HOSTconst 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 developmentdevtool: 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,// }// },//endplugins: [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 assetsnew 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)// }// })// })//endmodule.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: './',
最后,npm run dev 或者 npm run build
测试环境自己配 跟 生产环境差不多,就几个配置参数不一样
这个时候你会发现,特么的什么鬼文章 报错了啊,稍安勿躁~,两个地方,
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'),
还是版本问题
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-[email protected].10.0 --save-dev
npm run dev
总结:核心点就在创建并配置私服和修改出口入口配置,坑就在版本不兼容
建议:cli 一个 vue 的 demo 项目 从头撸一遍 再在实际项目里使用,而不是 copy 一下运行没问题搞定~ 建议而已,你怎么打人,呜呜呜~
原文:https://segmentfault.com/a/1190000015113584 作者:吃葡萄不吐番茄皮