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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
'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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
/** * 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
'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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
'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而走你的私服了
1 2 3 4 5 |
"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下面
1 |
assetsPublicPath: '/', => assetsPublicPath: './', |
最後,npm run dev 或者 npm run build
測試環境自己配 跟 生產環境差不多,就幾個配置引數不一樣
這個時候你會發現,特麼的什麼鬼文章 報錯了啊
稍安勿躁~
兩個地方,
- 1.webpack.dev.client.js
1 2 3 |
//雙路由 私服一層控制私服路由 vue的路由控制該頁面下的路由 app.use(router) 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後可正常訪問…
注:內容有不當或者錯誤處請指正~轉載請註明出處~謝謝合作!