1. 程式人生 > >vue構建多頁面應用例項程式碼分享

vue構建多頁面應用例項程式碼分享

最近一直在研究使用vue做出來一些東西,但都是SPA的單頁面應用,但實際工作中,單頁面並不一定符合業務需求,所以這篇我就來說說怎麼開發多頁面的Vue應用,以及在這個過程會遇到的問題。本文主要和大家介紹用vue構建多頁面應用的示例程式碼,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望能幫助到大家。

這是我放在GitHub上的專案,裡面有整個配置檔案,可以參看一下:multiple-vue-page

準備工作

在本地用vue-cli新建一個專案,這個步驟vue的官網上有,我就不再說了。

這裡有一個地方需要改一下,在執行npm install命令之前,在package.json裡新增一個依賴,後面會用到。

修改webpack配置

這裡展示一下我的專案目錄

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

├── README.md

├── build

│  ├── build.js

│  ├── check-versions.js

│  ├── dev-client.js

│  ├── dev-server.js

│  ├── utils.js

│  ├── vue-loader.conf.js

│  ├── webpack.base.conf.js

│  ├── webpack.dev.conf.js

│  └── webpack.prod.conf.js

├── config

│  ├── dev.env.js

│  ├── index.js

│  └── prod.env.js

├── package.json

├── src

│  ├── assets

│  │  └── logo.png

│  ├── components

│  │  ├── Hello.vue

│  │  └── cell.vue

│  └── pages

│    ├── cell

│    │  ├── cell.html

│    │  ├── cell.js

│    │  └── cell.vue

│    └── index

│      ├── index.html

│      ├── index.js

│      ├── index.vue

│      └── router

│        └── index.js

└── static

在這一步裡我們需要改動的檔案都在build檔案下,分別是:

  • utils.js

  • webpack.base.conf.js

  • webpack.dev.conf.js

  • webpack.prod.conf.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

// utils.js檔案

var path = require('path')

var config = require('../config')

var ExtractTextPlugin = require('extract-text-webpack-plugin')

exports.assetsPath = function (_path) {

var assetsSubDirectory = process.env.NODE_ENV === 'production' ?

config.build.assetsSubDirectory :

config.dev.assetsSubDirectory

return path.posix.join(assetsSubDirectory, _path)

}

exports.cssLoaders = function (options) {

options = options || {}

var cssLoader = {

loader: 'css-loader',

options: {

minimize: process.env.NODE_ENV === 'production',

sourceMap: options.sourceMap

}

}

// generate loader string to be used with extract text plugin

function generateLoaders(loader, loaderOptions) {

var loaders = [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) {

var output = []

var loaders = exports.cssLoaders(options)

for (var extension in loaders) {

var loader = loaders[extension]

output.push({

test: new RegExp('\\.' + extension + '$'),

use: loader

})

}

return output

}

/* 這裡是新增的部分 ---------------------------- 開始 */

// glob是webpack安裝時依賴的一個第三方模組,還模組允許你使用 *等符號, 例如lib/*.js就是獲取lib資料夾下的所有js字尾名的檔案

var glob = require('glob')

// 頁面模板

var HtmlWebpackPlugin = require('html-webpack-plugin')

// 取得相應的頁面路徑,因為之前的配置,所以是src資料夾下的pages資料夾

var PAGE_PATH = path.resolve(__dirname, '../src/pages')

// 用於做相應的merge處理

var merge = require('webpack-merge')

//多入口配置

// 通過glob模組讀取pages資料夾下的所有對應資料夾下的js字尾檔案,如果該檔案存在

// 那麼就作為入口處理

exports.entries = function () {

var entryFiles = glob.sync(PAGE_PATH + '/*/*.js')

var map = {}

entryFiles.forEach((filePath) => {

var filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'))

map[filename] = filePath

})

return map

}

//多頁面輸出配置

// 與上面的多頁面入口配置相同,讀取pages資料夾下的對應的html字尾檔案,然後放入陣列中

exports.htmlPlugin = function () {

let entryHtml = glob.sync(PAGE_PATH + '/*/*.html')

let arr = []

entryHtml.forEach((filePath) => {

let filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'))

let conf = {

// 模板來源

template: filePath,

// 檔名稱

filename: filename + '.html',

// 頁面模板需要加對應的js指令碼,如果不加這行則每個頁面都會引入所有的js指令碼

chunks: ['manifest', 'vendor', filename],

inject: true

}

if (process.env.NODE_ENV === 'production') {

conf = merge(conf, {

minify: {

removeComments: true,

collapseWhitespace: true,

removeAttributeQuotes: true

},

chunksSortMode: 'dependency'

})

}

arr.push(new HtmlWebpackPlugin(conf))

})

return arr

}

/* 這裡是新增的部分 ---------------------------- 結束 */

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

// webpack.base.conf.js 檔案

var path = require('path')

var utils = require('./utils')

var config = require('../config')

var vueLoaderConfig = require('./vue-loader.conf')

function resolve(dir) {

return path.join(__dirname, '..', dir)

}

module.exports = {

/* 修改部分 ---------------- 開始 */

entry: utils.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'),

'pages': resolve('src/pages'),

'components': resolve('src/components')

}

},

module: {

rules: [{

test: /\.vue$/,

loader: 'vue-loader',

options: vueLoaderConfig

},

{

test: /\.js$/,

loader: 'babel-loader',

include: [resolve('src'), resolve('test')]

},

{

test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,

loader: 'url-loader',

options: {

limit: 10000,

name: utils.assetsPath('img/[name].[hash:7].[ext]')

}

},

{

test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,

loader: 'url-loader',

options: {

limit: 10000,

name: utils.assetsPath('fonts/[name].[hash:7].[ext]')

}

}

]

}

}

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

var utils = require('./utils')

var webpack = require('webpack')

var config = require('../config')

var merge = require('webpack-merge')

var baseWebpackConfig = require('./webpack.base.conf')

var HtmlWebpackPlugin = require('html-webpack-plugin')

var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')

// add hot-reload related code to entry chunks

Object.keys(baseWebpackConfig.entry).forEach(function (name) {

baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])

})

module.exports = merge(baseWebpackConfig, {

module: {

rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })

},

// cheap-module-eval-source-map is faster for development

devtool: '#cheap-module-eval-source-map',

plugins: [

new webpack.DefinePlugin({

'process.env': config.dev.env

}),

// https://github.com/glenjamin/webpack-hot-middleware#installation--usage

new webpack.HotModuleReplacementPlugin(),

new webpack.NoEmitOnErrorsPlugin(),

// https://github.com/ampedandwired/html-webpack-plugin

/* 註釋這個區域的檔案 ------------- 開始 */

// new HtmlWebpackPlugin({

//  filename: 'index.html',

//  template: 'index.html',

//  inject: true

// }),

/* 註釋這個區域的檔案 ------------- 結束 */

new FriendlyErrorsPlugin()

/* 新增 .concat(utils.htmlPlugin()) ------------------ */

].concat(utils.htmlPlugin())

})

webpack.prod.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

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

var path = require('path')

var utils = require('./utils')

var webpack = require('webpack')

var config = require('../config')

var merge = require('webpack-merge')

var baseWebpackConfig = require('./webpack.base.conf')

var CopyWebpackPlugin = require('copy-webpack-plugin')

var HtmlWebpackPlugin = require('html-webpack-plugin')

var ExtractTextPlugin = require('extract-text-webpack-plugin')

var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')

var env = config.build.env

var webpackConfig = merge(baseWebpackConfig, {

module: {

rules: utils.styleLoaders({

sourceMap: config.build.productionSourceMap,

extract: true

})

},

devtool: config.build.productionSourceMap ? '#source-map' : false,

output: {

path: config.build.assetsRoot,

filename: utils.assetsPath('js/[name].[chunkhash].js'),

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 webpack.optimize.UglifyJsPlugin({

compress: {

warnings: false

},

sourceMap: true

}),

// extract css into its own file

new ExtractTextPlugin({

filename: utils.assetsPath('css/[name].[contenthash].css')

}),

// Compress extracted CSS. We are using this plugin so that possible

// duplicated CSS from different components can be deduped.

new OptimizeCSSPlugin({

cssProcessorOptions: {

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

/* 註釋這個區域的內容 ---------------------- 開始 */

// new HtmlWebpackPlugin({

//  filename: config.build.index,

//  template: 'index.html',

//  inject: true,

//  minify: {

//   removeComments: true,

//   collapseWhitespace: true,

//   removeAttributeQuotes: true

//   // more options:

//   // https://github.com/kangax/html-minifier#options-quick-reference

//  },

//  // necessary to consistently work with multiple chunks via CommonsChunkPlugin

//  chunksSortMode: 'dependency'

// }),

/* 註釋這個區域的內容 ---------------------- 結束 */

// split vendor js into its own file

new webpack.optimize.CommonsChunkPlugin({

name: 'vendor',

minChunks: function (module, count) {

// 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',

chunks: ['vendor']

}),

// copy custom static assets

new CopyWebpackPlugin([{

from: path.resolve(__dirname, '../static'),

to: config.build.assetsSubDirectory,

ignore: ['.*']

}])

/* 該位置新增 .concat(utils.htmlPlugin()) ------------------- */

].concat(utils.htmlPlugin())

})

if (config.build.productionGzip) {

var 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) {

var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

webpackConfig.plugins.push(new BundleAnalyzerPlugin())

}

module.exports = webpackConfig

至此,webpack的配置就結束了。

但是還沒完啦,下面繼續。

檔案結構

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

├── src

│  ├── assets

│  │  └── logo.png

│  ├── components

│  │  ├── Hello.vue

│  │  └── cell.vue

│  └── pages

│    ├── cell

│    │  ├── cell.html

│    │  ├── cell.js

│    │  └── cell.vue

│    └── index

│      ├── index.html

│      ├── index.js

│      ├── index.vue

│      └── router

│        └── index.js

src就是我所使用的工程檔案了,assets,components,pages分別是靜態資原始檔、元件檔案、頁面檔案。

前兩個就不多說,主要是頁面檔案裡,我目前是按照專案的模組分的資料夾,你也可以按照你自己的需求調整。然後在每個模組裡又有三個內容:vue檔案,js檔案和html檔案。這三個檔案的作用就相當於做spa單頁面應用時,根目錄的index.html頁面模板,src檔案下的main.js和app.vue的功能。

原先,入口檔案只有一個main.js,但現在由於是多頁面,因此入口頁面多了,我目前就是兩個:index和cell,之後如果打包,就會在dist檔案下生成兩個HTML檔案:index.html和cell.html(可以參考一下單頁面應用時,打包只會生成一個index.html,區別在這裡)。

cell檔案下的三個檔案,就是一般模式的配置,參考index的就可以,但並不完全相同。

特別注意的地方

cell.js

在這個檔案裡,按照寫法,應該是這樣的吧:

1

2

3

4

5

6

7

8

import Vue from 'Vue'

import cell from './cell.vue'

new Vue({

el:'#app'// 這裡參考cell.html和cell.vue的根節點id,保持三者一致

teleplate:'<cell/>',

components:{ cell }

})

這個配置在執行時(npm run dev)會報錯

[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
(found in <Root>)

網上的解釋是這樣的:

執行時構建不包含模板編譯器,因此不支援 template 選項,只能用 render 選項,但即使使用執行時構建,在單檔案元件中也依然可以寫模板,因為單檔案元件的模板會在構建時預編譯為 render 函式。執行時構建比獨立構建要輕量30%,只有 17.14 Kb min+gzip大小。

上面一段是官方api中的解釋。就是說,如果我們想使用template,我們不能直接在客戶端使用npm install之後的vue。
也給出了相應的修改方案:

1

resolve: { alias: { 'vue': 'vue/dist/vue.js' } }

這裡是修改package.json的resolve下的vue的配置,很多人反應這樣修改之後就好了,但是我按照這個方法修改之後依然報錯。然後我就想到上面提到的render函式,因此我的修改是針對cell.js檔案的。

1

2

3

4

5

6

7

8

import Vue from 'Vue'

import cell from './cell.vue'

/* eslint-disable no-new */

new Vue({

el: '#app',

render: h => h(cell)

})

這裡面我用render函式取代了元件的寫法,在執行就沒問題了。

頁面跳轉

既然是多頁面,肯定涉及頁面之間的互相跳轉,就按照我這個專案舉例,從index.html檔案點選a標籤跳轉到cell.html。

我最開始寫的是:

1

2

<!-- index.html -->

<a href='../cell/cell.html'></a>

但這樣寫,不論是在開發環境還是最後測試,都會報404,找不到這個頁面。

改成這樣既可:

1

2

<!-- index.html -->

<a href='cell.html'></a>

這樣他就會自己找cell.html這個檔案。

打包後的資源路徑

執行npm run build之後,開啟相應的html檔案你是看不到任何東西的,檢視原因是找不到相應的js檔案和css檔案。

這時候的檔案結構是這樣的:

1

2

3

4

5

├── dist

│  ├── js

│  ├── css

│  ├── index.html

│  └── cell.html

檢視index.html檔案之後會發現資源的引用路徑是:

/dist/js.........

這樣,如果你的dist檔案不是在根目錄下的,就根本找不到資源。

方法當然也有啦,如果你不嫌麻煩,就一個檔案一個檔案的修改路徑咯,或者像我一樣偷懶,修改config下的index.js檔案。具體的做法是:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

build: {

env: require('./prod.env'),

index: path.resolve(__dirname, '../dist/index.html'),

assetsRoot: path.resolve(__dirname, '../dist'),

assetsSubDirectory: 'static',

assetsPublicPath: '/',

productionSourceMap: true,

// Gzip off by default as many popular static hosts such as

// Surge or Netlify already gzip all static assets for you.

// Before setting to `true`, make sure to:

// npm install --save-dev compression-webpack-plugin

productionGzip: false,

productionGzipExtensions: ['js', 'css'],

// Run the build command with an extra argument to

// View the bundle analyzer report after build finishes:

// `npm run build --report`

// Set to `true` or `false` to always turn it on or off

bundleAnalyzerReport: process.env.npm_config_report

},

將這裡面的

1

assetsPublicPath: '/',

改成

1

assetsPublicPath: './',

醬紫,配置檔案資源的時候找到的就是相對路徑下的資源了,在重新npm run build看看吧。