用vue构建多页面应用
最近一直在研究使用vue做出来一些东西,但都是SPA的单页面应用,但实际工作中,单页面并不一定符合业务需求,所以这篇我就来说说怎么开发多页面的Vue应用,以及在这个过程会遇到的问题。
准备工作
在本地用vue-cli
新建一个项目,这个步骤vue的官网上有,我就不再说了。
这里有一个地方需要改一下,在执行npm install
命令之前,在package.json
里添加一个依赖,后面会用到。
修改webpack配置
这里展示一下我的项目目录
1 ├── README.md2 ├── 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 ├── src2 │ ├── 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.js
和app.vue
的功能。
原先,入口文件只有一个main.js,但现在由于是多页面,因此入口页面多了,我目前就是两个:index和cell,之后如果打包,就会在dist
文件下生成两个HTML文件:index.html
和cell.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 ├── dist2 │ ├── 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