這樣對比的話,單頁面的優點確實很大,但當我本身去打開某寶,某東的移動端頁面時,確實它們都是多頁面應用。爲何?我能想到的就幾點:
1.單頁面使用的技術對低版本的瀏覽器不友好,大公司還得兼顧使用低版本瀏覽器的用戶啊css
2.功能模塊開發來講,好比說單頁面的業務公用組件,有時候你都不知道分給誰開發html
3.seo優化吧(PS:既然是大應用應該不少人都知道,爲何還要作搜索引擎優化)vue
思路:node
因爲vue-cli已經寫好了單頁面的webpack文件,不去改動以前是它默認的一個頁面引用打包的資源。既然是多頁面,那麼把webpack入口文件改爲多個就行了啊。未改動時的webpack.base.conf.js(這個JS的功能主要在於全局配置,好比入口文件,出口文件,解析規則等)
webpack
// 把箭頭部分的入口文件改成如下 entry: { 'index': '..../main.js' // 注意省略號是實際開發時的項目路徑 'product': '..../main.js' }
/** * 獲取多頁面入口文件 * @globPath 文件路徑 */ const glob = require('glob') function getEntries(globPath) { const entries = glob.sync(globPath).reduce((result, entry) => { const moduleName = path.basename(path.dirname(entry)) // 獲取模塊名稱 result[moduleName] = entry return result }, {}) return entries }
測試一下這個函數git
'use strict' const path = require('path') const utils = require('./utils') const config = require('../config') const vueLoaderConfig = require('./vue-loader.conf') function resolve (dir) { return path.join(__dirname, '..', dir) } const glob = require('glob') function getEntries (globPath){ const entries = glob.sync(globPath).reduce((result, entry) => { const moduleName = path.basename(path.dirname(entry)) // 獲取模塊名稱 result[moduleName] = entry return result }, {}) return entries } const entries = getEntries('./src/modules/**/*.js') module.exports = { context: path.resolve(__dirname, '../'), entry: entries, // 改動部分 output: { path: config.build.assetsRoot, filename: '[name].js', publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath }, resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', '@': resolve('src'), } }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', options: vueLoaderConfig }, { test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('media/[name].[hash:7].[ext]') } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } } ] }, node: { // prevent webpack from injecting useless setImmediate polyfill because Vue // source contains it (although only uses it if it's native). setImmediate: false, // prevent webpack from injecting mocks to Node native modules // that does not make sense for the client dgram: 'empty', fs: 'empty', net: 'empty', tls: 'empty', child_process: 'empty' } }
/** * 頁面打包 * @entries 打包文件 * @config 參數配置 * @module 使用的主體 */ const HtmlWebpackPlugin = require('html-webpack-plugin') function pack (entries, module) { for (const path in entries) { const conf = { filename: `modules/${path}/index.html`, template: entries[path], // 模板路徑 inject: true, chunks: ['manifest', 'vendor', path] // 必須先引入公共依賴 } module.plugins.push(new HtmlWebpackPlugin(conf)) } }
'use strict' const path = require('path') const utils = require('./utils') const webpack = require('webpack') const config = require('../config') const merge = require('webpack-merge') const baseWebpackConfig = require('./webpack.base.conf') const CopyWebpackPlugin = require('copy-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const ExtractTextPlugin = require('extract-text-webpack-plugin') const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') const UglifyJsPlugin = require('uglifyjs-webpack-plugin') const env = require('../config/prod.env') const glob = require('glob') function getEntries (globPath){ const entries = glob.sync(globPath).reduce((result, entry) => { const moduleName = path.basename(path.dirname(entry)) // 獲取模塊名稱 result[moduleName] = entry return result }, {}) return entries } const entries = getEntries('./src/modules/**/*.html') // 獲取多頁面全部入口文件 const webpackConfig = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true, usePostCSS: true }) }, devtool: config.build.productionSourceMap ? config.build.devtool : false, output: { path: config.build.assetsRoot, filename: 'modules/[name]/[name].[chunkhash].js', // publicPath: '/' // 改成相對路徑 // chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') }, plugins: [ // http://vuejs.github.io/vue-loader/en/workflow/production.html new webpack.DefinePlugin({ 'process.env': env }), new UglifyJsPlugin({ uglifyOptions: { compress: { warnings: false } }, sourceMap: config.build.productionSourceMap, parallel: true }), // extract css into its own file new ExtractTextPlugin({ filename: utils.assetsPath('css/[name].[contenthash].css'), // Setting the following option to `false` will not extract CSS from codesplit chunks. // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 allChunks: true, }), // Compress extracted CSS. We are using this plugin so that possible // duplicated CSS from different components can be deduped. new OptimizeCSSPlugin({ cssProcessorOptions: config.build.productionSourceMap ? { safe: true, map: { inline: false } } : { safe: true } }), // generate dist index.html with correct asset hash for caching. // you can customize output by editing /index.html // see https://github.com/ampedandwired/html-webpack-plugin // keep module.id stable when vendor modules does not change new webpack.HashedModuleIdsPlugin(), // enable scope hoisting new webpack.optimize.ModuleConcatenationPlugin(), // split vendor js into its own file new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks (module) { // any required modules inside node_modules are extracted to vendor return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 ) } }), // extract webpack runtime and module manifest to its own file in order to // prevent vendor hash from being updated whenever app bundle is updated new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', minChunks: Infinity }), // This instance extracts shared chunks from code splitted chunks and bundles them // in a separate chunk, similar to the vendor chunk // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk new webpack.optimize.CommonsChunkPlugin({ name: 'app', async: 'vendor-async', children: true, minChunks: 3 }), // copy custom static assets new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../static'), to: config.build.assetsSubDirectory, ignore: ['.*'] } ]) ] }) if (config.build.productionGzip) { const CompressionWebpackPlugin = require('compression-webpack-plugin') webpackConfig.plugins.push( new CompressionWebpackPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', test: new RegExp( '\\.(' + config.build.productionGzipExtensions.join('|') + ')$' ), threshold: 10240, minRatio: 0.8 }) ) } if (config.build.bundleAnalyzerReport) { const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin webpackConfig.plugins.push(new BundleAnalyzerPlugin()) } function pack (entries, module) { for (const path in entries) { const conf = { filename: `modules/${path}/index.html`, template: entries[path], // 模板路徑 inject: true, chunks: ['manifest', 'vendor', path] // 必須先引入公共依賴 } module.plugins.push(new HtmlWebpackPlugin(conf)) } } pack(entries, webpackConfig) module.exports = webpackConfig