用vue构建多页面应用

vue

最近一直在研究使用vue做出来一些东西,但都是SPA的单页面应用,但实际工作中,单页面并不一定符合业务需求,所以这篇我就来说说怎么开发多页面的Vue应用,以及在这个过程会遇到的问题。

准备工作

在本地用vue-cli新建一个项目,这个步骤vue的官网上有,我就不再说了。

这里有一个地方需要改一下,在执行npm install命令之前,在package.json里添加一个依赖,后面会用到。

修改webpack配置

这里展示一下我的项目目录

 1 ├── README.md

2 ├── build

3 │ ├── build.js

4 │ ├── check-versions.js

5 │ ├── dev-client.js

6 │ ├── dev-server.js

7 │ ├── utils.js

8 │ ├── vue-loader.conf.js

9 │ ├── webpack.base.conf.js

10 │ ├── webpack.dev.conf.js

11 │ └── webpack.prod.conf.js

12 ├── config

13 │ ├── dev.env.js

14 │ ├── index.js

15 │ └── prod.env.js

16 ├── package.json

17 ├── src

18 │ ├── assets

19 │ │ └── logo.png

20 │ ├── components

21 │ │ ├── Hello.vue

22 │ │ └── cell.vue

23 │ └── pages

24 │ ├── cell

25 │ │ ├── cell.html

26 │ │ ├── cell.js

27 │ │ └── cell.vue

28 │ └── index

29 │ ├── index.html

30 │ ├── index.js

31 │ ├── index.vue

32 │ └── router

33 │ └── index.js

34 └── static

在这一步里我们需要改动的文件都在build文件下,分别是:

  • utils.js
  • webpack.base.conf.js
  • webpack.dev.conf.js
  • webpack.prod.conf.js

我就按照顺序放出完整的文件内容,然后在做修改或添加的位置用注释符标注出来:

utils.js文件

  1 // utils.js文件

2

3 var path = require(\'path\')

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

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

6

7 exports.assetsPath = function (_path) {

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

9 config.build.assetsSubDirectory :

10 config.dev.assetsSubDirectory

11 return path.posix.join(assetsSubDirectory, _path)

12 }

13

14 exports.cssLoaders = function (options) {

15 options = options || {}

16

17 var cssLoader = {

18 loader: \'css-loader\',

19 options: {

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

21 sourceMap: options.sourceMap

22 }

23 }

24

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

26 function generateLoaders(loader, loaderOptions) {

27 var loaders = [cssLoader]

28 if (loader) {

29 loaders.push({

30 loader: loader + \'-loader\',

31 options: Object.assign({}, loaderOptions, {

32 sourceMap: options.sourceMap

33 })

34 })

35 }

36

37 // Extract CSS when that option is specified

38 // (which is the case during production build)

39 if (options.extract) {

40 return ExtractTextPlugin.extract({

41 use: loaders,

42 fallback: \'vue-style-loader\'

43 })

44 } else {

45 return [\'vue-style-loader\'].concat(loaders)

46 }

47 }

48

49 // https://vue-loader.vuejs.org/en/configurations/extract-css.html

50 return {

51 css: generateLoaders(),

52 postcss: generateLoaders(),

53 less: generateLoaders(\'less\'),

54 sass: generateLoaders(\'sass\', { indentedSyntax: true }),

55 scss: generateLoaders(\'sass\'),

56 stylus: generateLoaders(\'stylus\'),

57 styl: generateLoaders(\'stylus\')

58 }

59 }

60

61 // Generate loaders for standalone style files (outside of .vue)

62 exports.styleLoaders = function (options) {

63 var output = []

64 var loaders = exports.cssLoaders(options)

65 for (var extension in loaders) {

66 var loader = loaders[extension]

67 output.push({

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

69 use: loader

70 })

71 }

72 return output

73 }

74

75 /* 这里是添加的部分 ---------------------------- 开始 */

76

77 // glob是webpack安装时依赖的一个第三方模块,还模块允许你使用 *等符号, 例如lib/*.js就是获取lib文件夹下的所有js后缀名的文件

78 var glob = require(\'glob\')

79 // 页面模板

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

81 // 取得相应的页面路径,因为之前的配置,所以是src文件夹下的pages文件夹

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

83 // 用于做相应的merge处理

84 var merge = require(\'webpack-merge\')

85

86

87 //多入口配置

88 // 通过glob模块读取pages文件夹下的所有对应文件夹下的js后缀文件,如果该文件存在

89 // 那么就作为入口处理

90 exports.entries = function () {

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

92 var map = {}

93 entryFiles.forEach((filePath) => {

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

95 map[filename] = filePath

96 })

97 return map

98 }

99

100 //多页面输出配置

101 // 与上面的多页面入口配置相同,读取pages文件夹下的对应的html后缀文件,然后放入数组中

102 exports.htmlPlugin = function () {

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

104 let arr = []

105 entryHtml.forEach((filePath) => {

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

107 let conf = {

108 // 模板来源

109 template: filePath,

110 // 文件名称

111 filename: filename + \'.html\',

112 // 页面模板需要加对应的js脚本,如果不加这行则每个页面都会引入所有的js脚本

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

114 inject: true

115 }

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

117 conf = merge(conf, {

118 minify: {

119 removeComments: true,

120 collapseWhitespace: true,

121 removeAttributeQuotes: true

122 },

123 chunksSortMode: \'dependency\'

124 })

125 }

126 arr.push(new HtmlWebpackPlugin(conf))

127 })

128 return arr

129 }

130 /* 这里是添加的部分 ---------------------------- 结束 */

webpack.base.conf.js 文件

 1 // webpack.base.conf.js 文件

2

3 var path = require(\'path\')

4 var utils = require(\'./utils\')

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

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

7

8 function resolve(dir) {

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

10 }

11

12 module.exports = {

13 /* 修改部分 ---------------- 开始 */

14 entry: utils.entries(),

15 /* 修改部分 ---------------- 结束 */

16 output: {

17 path: config.build.assetsRoot,

18 filename: \'[name].js\',

19 publicPath: process.env.NODE_ENV === \'production\' ?

20 config.build.assetsPublicPath :

21 config.dev.assetsPublicPath

22 },

23 resolve: {

24 extensions: [\'.js\', \'.vue\', \'.json\'],

25 alias: {

26 \'vue$\': \'vue/dist/vue.esm.js\',

27 \'@\': resolve(\'src\'),

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

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

30 }

31 },

32 module: {

33 rules: [{

34 test: /\.vue$/,

35 loader: \'vue-loader\',

36 options: vueLoaderConfig

37 },

38 {

39 test: /\.js$/,

40 loader: \'babel-loader\',

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

42 },

43 {

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

45 loader: \'url-loader\',

46 options: {

47 limit: 10000,

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

49 }

50 },

51 {

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

53 loader: \'url-loader\',

54 options: {

55 limit: 10000,

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

57 }

58 }

59 ]

60 }

61 }

webpack.dev.conf.js 文件

  1 var utils = require(\'./utils\')

2 var webpack = require(\'webpack\')

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

4 var merge = require(\'webpack-merge\')

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

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

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

8

9 // add hot-reload related code to entry chunks

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

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

12 })

13

14 module.exports = merge(baseWebpackConfig, {

15 module: {

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

17 },

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

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

20 plugins: [

21 new webpack.DefinePlugin({

22 \'process.env\': config.dev.env

23 }),

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

25 new webpack.HotModuleReplacementPlugin(),

26 new webpack.NoEmitOnErrorsPlugin(),

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

28 /* 注释这个区域的文件 ------------- 开始 */

29 // new HtmlWebpackPlugin({

30 // filename: \'index.html\',

31 // template: \'index.html\',

32 // inject: true

33 // }),

34 /* 注释这个区域的文件 ------------- 结束 */

35 new FriendlyErrorsPlugin()

36

37 /* 添加 .concat(utils.htmlPlugin()) ------------------ */

38 ].concat(utils.htmlPlugin())

39 })

40 webpack.prod.conf.js 文件

41 var path = require(\'path\')

42 var utils = require(\'./utils\')

43 var webpack = require(\'webpack\')

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

45 var merge = require(\'webpack-merge\')

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

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

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

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

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

51

52 var env = config.build.env

53

54 var webpackConfig = merge(baseWebpackConfig, {

55 module: {

56 rules: utils.styleLoaders({

57 sourceMap: config.build.productionSourceMap,

58 extract: true

59 })

60 },

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

62 output: {

63 path: config.build.assetsRoot,

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

65 chunkFilename: utils.assetsPath(\'js/[id].[chunkhash].js\')

66 },

67 plugins: [

68 // http://vuejs.github.io/vue-loader/en/workflow/production.html

69 new webpack.DefinePlugin({

70 \'process.env\': env

71 }),

72 new webpack.optimize.UglifyJsPlugin({

73 compress: {

74 warnings: false

75 },

76 sourceMap: true

77 }),

78 // extract css into its own file

79 new ExtractTextPlugin({

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

81 }),

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

83 // duplicated CSS from different components can be deduped.

84 new OptimizeCSSPlugin({

85 cssProcessorOptions: {

86 safe: true

87 }

88 }),

89 // generate dist index.html with correct asset hash for caching.

90 // you can customize output by editing /index.html

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

92

93 /* 注释这个区域的内容 ---------------------- 开始 */

94 // new HtmlWebpackPlugin({

95 // filename: config.build.index,

96 // template: \'index.html\',

97 // inject: true,

98 // minify: {

99 // removeComments: true,

100 // collapseWhitespace: true,

101 // removeAttributeQuotes: true

102 // // more options:

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

104 // },

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

106 // chunksSortMode: \'dependency\'

107 // }),

108 /* 注释这个区域的内容 ---------------------- 结束 */

109

110 // split vendor js into its own file

111 new webpack.optimize.CommonsChunkPlugin({

112 name: \'vendor\',

113 minChunks: function (module, count) {

114 // any required modules inside node_modules are extracted to vendor

115 return (

116 module.resource &&

117 /\.js$/.test(module.resource) &&

118 module.resource.indexOf(

119 path.join(__dirname, \'../node_modules\')

120 ) === 0

121 )

122 }

123 }),

124 // extract webpack runtime and module manifest to its own file in order to

125 // prevent vendor hash from being updated whenever app bundle is updated

126 new webpack.optimize.CommonsChunkPlugin({

127 name: \'manifest\',

128 chunks: [\'vendor\']

129 }),

130 // copy custom static assets

131 new CopyWebpackPlugin([{

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

133 to: config.build.assetsSubDirectory,

134 ignore: [\'.*\']

135 }])

136 /* 该位置添加 .concat(utils.htmlPlugin()) ------------------- */

137 ].concat(utils.htmlPlugin())

138 })

139

140 if (config.build.productionGzip) {

141 var CompressionWebpackPlugin = require(\'compression-webpack-plugin\')

142

143 webpackConfig.plugins.push(

144 new CompressionWebpackPlugin({

145 asset: \'[path].gz[query]\',

146 algorithm: \'gzip\',

147 test: new RegExp(

148 \'\\.(\' +

149 config.build.productionGzipExtensions.join(\'|\') +

150 \')$\'

151 ),

152 threshold: 10240,

153 minRatio: 0.8

154 })

155 )

156 }

157

158 if (config.build.bundleAnalyzerReport) {

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

160 webpackConfig.plugins.push(new BundleAnalyzerPlugin())

161 }

162

163 module.exports = webpackConfig

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

但是还没完啦,下面继续。

文件结构

 1 ├── src

2 │ ├── assets

3 │ │ └── logo.png

4 │ ├── components

5 │ │ ├── Hello.vue

6 │ │ └── cell.vue

7 │ └── pages

8 │ ├── cell

9 │ │ ├── cell.html

10 │ │ ├── cell.js

11 │ │ └── cell.vue

12 │ └── index

13 │ ├── index.html

14 │ ├── index.js

15 │ ├── index.vue

16 │ └── router

17 │ └── index.js

src就是我所使用的工程文件了,assets,components,pages分别是静态资源文件、组件文件、页面文件。

前两个就不多说,主要是页面文件里,我目前是按照项目的模块分的文件夹,你也可以按照你自己的需求调整。然后在每个模块里又有三个内容:vue文件,js文件和html文件。这三个文件的作用就相当于做spa单页面应用时,根目录的index.html页面模板,src文件下的main.jsapp.vue的功能。

原先,入口文件只有一个main.js,但现在由于是多页面,因此入口页面多了,我目前就是两个:index和cell,之后如果打包,就会在dist文件下生成两个HTML文件:index.htmlcell.html(可以参考一下单页面应用时,打包只会生成一个index.html,区别在这里)。

cell文件下的三个文件,就是一般模式的配置,参考index的就可以,但并不完全相同。

特别注意的地方

cell.js

在这个文件里,按照写法,应该是这样的吧:

1 import Vue from \'Vue\'

2 import cell from \'./cell.vue\'

3

4 new Vue({

5 el:\'#app\',// 这里参考cell.html和cell.vue的根节点id,保持三者一致

6 teleplate:\'<cell/>\',

7 components:{ cell }

8 })

这个配置在运行时(npm run dev)会报错

1 [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.

2 (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 import Vue from \'Vue\'

2 import cell from \'./cell.vue\'

3

4 /* eslint-disable no-new */

5 new Vue({

6 el: \'#app\',

7 render: h => h(cell)

8 })

这里面我用render函数取代了组件的写法,在运行就没问题了。

页面跳转

既然是多页面,肯定涉及页面之间的互相跳转,就按照我这个项目举例,从index.html文件点击a标签跳转到cell.html。

我最开始写的是:

1  <!-- index.html -->

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

但这样写,不论是在开发环境还是最后测试,都会报404,找不到这个页面。

改成这样既可:

1  <!-- index.html -->

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

这样他就会自己找cell.html这个文件。

打包后的资源路径

执行npm run build之后,打开相应的html文件你是看不到任何东西的,查看原因是找不到相应的js文件和css文件。

这时候的文件结构是这样的:

1 ├── dist

2 │ ├── js

3 │ ├── css

4 │ ├── index.html

5 │ └── cell.html

查看index.html文件之后会发现资源的引用路径是:

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

这样,如果你的dist文件不是在根目录下的,就根本找不到资源。

方法当然也有啦,如果你不嫌麻烦,就一个文件一个文件的修改路径咯,或者像我一样偷懒,修改config下的index.js文件。具体的做法是:

 1 build: {

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

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

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

5 assetsSubDirectory: \'static\',

6 assetsPublicPath: \'/\',

7 productionSourceMap: true,

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

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

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

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

12 productionGzip: false,

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

14 // Run the build command with an extra argument to

15 // View the bundle analyzer report after build finishes:

16 // `npm run build --report`

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

18 bundleAnalyzerReport: process.env.npm_config_report

19 },

将这里面的

1 assetsPublicPath: \'/\',

改成

1 assetsPublicPath: \'./\',

以上内容就是实际项目运用的,这就可以啦,在重新npm run build 试试看

以上是 用vue构建多页面应用 的全部内容, 来源链接: utcz.com/z/377997.html

回到顶部