ljx20180807

最近一直在研究使用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 试试看

分类:

技术点:

相关文章: