脚手架vue-cli
[[toc]]
# 安装 Vue CLI 3.x yarn global @vue/cli
# my-project 是你的项目名称 vue create my-project
|
vue-cli 中包含着 typescript 选项,只需要选择即可
vue create repo # 手动配置的时候需要选择 TypeScript Check the features needed for your project: ◉ Babel ◉ TypeScript ◯ Progressive Web App (PWA) Support ◯ Router ◉ Vuex ◉ CSS Pre-processors ◉ Linter / Formatter ◯ Unit Testing ◯ E2E Testing
|
vue.config.js 的配置
module.exports = { outputDir: './dist', lintOnSave: true, productionSourceMap: false, devServer: { port: 8082, open: true, host: '0.0.0.0', https: false, proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true, secure: false } } } };
|
开启Gzip压缩
const isPro = process.env.NODE_ENV === 'production'
module.exports = { ... configureWebpack: config => { if (isPro) { return { plugins: [ new CompressionWebpackPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', test: new RegExp( '\\.(js|css)$' ), threshold: 10240, minRatio: 0.8, }) ] } } } ... }
|
分析包文件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
chainWebpack: config => { config.resolve.symlinks(true); if (isAnalyze) { config .plugin('webpack-bundle-analyzer') .use( new BundleAnalyzerPlugin({ analyzerPort: 9999, openAnalyzer: true, })) } }
|
拆包
configureWebpack: () => ({ optimization: { splitChunks: { cacheGroups: { vendor:{ chunks:"all", test: /node_modules/, name:"vendor", minChunks: 1, maxInitialRequests: 5, minSize: 0, priority:100, }, common: { chunks:"all", test:/[\\/]src[\\/]js[\\/]/, name: "common", minChunks: 2, maxInitialRequests: 5, minSize: 0, priority:60 }, styles: { name: 'styles', test: /\.(sa|sc|c)ss$/, chunks: 'all', enforce: true, }, runtimeChunk: { name: 'manifest' } } } } })
|
默认插件简介
通过对 vue.config.js
的了解,我们知道了 vue-cli 3.x
为我们默认封装了项目运行的常用 webpack
配置,那么它给我们提供了哪些默认插件,每一个plugin
又有着怎样的用途呢?除了使用 vue inspect plugins 我们还可以通过运行 vue ui 进入可视化页面查看,步骤如下
- 打开可视化页面,点击对应项目进入管理页面(如果没有对应项目,需要导入或新建)
- 点击侧边栏
Tasks
选项,再点击二级栏 inspect
选项
- 点击
Run task
按钮执行审查命令
最后我们从输出的内容中找到 plugins
数组,其包含了如下插件(配置项已经省略,增加了定义插件的代码):
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const { DefinePlugin } = require('webpack');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssnanoPlugin = require('optimize-css-assets-webpack-plugin');
const { HashedModuleIdsPlugin } = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const PreloadPlugin = require('preload-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = { plugins: [ new VueLoaderPlugin(), new DefinePlugin(), new CaseSensitivePathsPlugin(), new FriendlyErrorsWebpackPlugin(), new MiniCssExtractPlugin(), new OptimizeCssnanoPlugin(), new HashedModuleIdsPlugin(), new HtmlWebpackPlugin(), new PreloadPlugin(), new CopyWebpackPlugin() ] }
|
使用 alias 简化路径
而在 CLI 3.x
中我们无法直接操作 webpack
的配置文件,我们需要通过 chainWebpack
来进行间接修改,代码如下
module.exports = { chainWebpack: config => { config.resolve.alias .set('@', resolve('src')) .set('_lib', resolve('src/common')) .set('_com', resolve('src/components')) .set('_img', resolve('src/images')) .set('_ser', resolve('src/services')) }, }
|
这样我们修改 webpack alias
来简化路径的优化就实现了。
但是需要注意的是对于在样式及 htm
l 模板中引用路径的简写时,前面需要加上 ~
符,否则路径解析会失败,如:
.img { background: (~_img/home.png); }
|
构建多页应用
多入口
在单页应用中,我们的入口文件只有一个,CLI
默认配置的是 main.js
,但是到了多页应用,
我们的入口文件便包含了 page1.js、page2.js、index.js
等,数量取决于 pages 文件夹下目录的个数,
这时候为了项目的可拓展性,我们需要自动计算入口文件的数量并解析路径配置到 webpack
中的 entry
属性上,如:
module.exports = { entry: { page1: '/xxx/pages/page1/page1.js', page2: '/xxx/pages/page2/page2.js', index: '/xxx/pages/index/index.js', }, }
|
那么我们如何读取并解析这样的路径呢,这里就需要使用工具和函数来解决了。
我们可以在根目录新建 build
文件夹存放 utils.js
这样共用的 webpack
功能性文件,并加入多入口读取解析方法
/* utils.js */ const path = require('path');
// glob 是 webpack 安装时依赖的一个第三方模块,该模块允许你使用 * 等符号, // 例如 lib/*.js 就是获取 lib 文件夹下的所有 js 后缀名的文件 const glob = require('glob');
// 取得相应的页面路径,因为之前的配置,所以是 src 文件夹下的 pages 文件夹 const PAGE_PATH = path.resolve(__dirname, '../src/pages');
/* * 多入口配置 * 通过 glob 模块读取 pages 文件夹下的所有对应文件夹下的 js * 后缀文件,如果该文件存在 * 那么就作为入口处理 */ exports.getEntries = () => { let entryFiles = glob.sync(PAGE_PATH + '/*/*.js') // 同步读取所有入口文件 let map = {} // 遍历所有入口文件 entryFiles.forEach(filePath => { // 获取文件名 let filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.')) // 以键值对的形式存储 map[filename] = filePath }) return map }
|
/* vue.config.js */
const utils = require('./build/utils')
module.exports = { // ... configureWebpack: config => { config.entry = utils.getEntries() }, // ... }
|
多模板
相对于多入口来说,多模板的配置也是大同小异,这里所说的模板便是每个page
下的html
模板文件,
而模板文件的作用主要用于 webpack
中 html-webpack-plugin
插件的配置,
其会根据模板文件生产一个编译后的 html
文件并自动加入携带 hash
的脚本和样式,基本配置如下
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = { plugins: [ new HtmlWebpackPlugin({ title: 'My Page', filename: 'demo.html', template: 'xxx/xxx/demo.html', chunks: ['manifest', 'vendor', 'demo'], inject: true, }) ] }
|
exports.htmlPlugin = configs => { 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', chunks: ['manifest', 'vendor', filename], inject: true, } if (configs) { conf = merge(conf, configs) } if (process.env.NODE_ENV === 'production') { conf = merge(conf, { minify: { removeComments: true, collapseWhitespace: true, }, chunksSortMode: 'manual' }) } arr.push(new HtmlWebpackPlugin(conf)) }) return arr }
|
/* vue.config.js */
const utils = require('./build/utils')
module.exports = { // ... configureWebpack: config => { config.entry = utils.getEntries() // 直接覆盖 entry 配置 // 使用 return 一个对象会通过 webpack-merge 进行合并,plugins 不会置空 return { plugins: [...utils.htmlPlugin()] } }, // ... }
|
如此我们多页应用的多入口和多模板的配置就完成了,
这时候我们运行命令 yarn build
后你会发现 dist
目录下生成了 3 个 html 文件,分别是 index.html、page1.html 和 page2.html
使用 pages 配置
其实,在 vue.config.js
中,我们还有一个配置没有使用,便是 pages
。pages
对象允许我们为应用配置多个入口及模板,
这就为我们的多页应用提供了开放的配置入口。官方示例代码如下
module.exports = { pages: { index: { entry: 'src/index/main.js', template: 'public/index.html', filename: 'index.html', title: 'Index Page', chunks: ['chunk-vendors', 'chunk-common', 'index'] }, subpage: 'src/subpage/main.js' } }
|
我们不难发现,pages
对象中的 key
就是入口的别名,而其 value
对象其实是入口 entry
和模板属性的合并,
这样我们上述介绍的获取多入口和多模板的方法就可以合并成一个函数来进行多页的处理,合并后的 setPages
方法如下
/ pages 多入口配置 exports.setPages = configs => { let entryFiles = glob.sync(PAGE_PATH + '/*/*.js') let map = {}
entryFiles.forEach(filePath => { let filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.')) let tmp = filePath.substring(0, filePath.lastIndexOf('\/'))
let conf = { entry: filePath, template: tmp + '.html', filename: filename + '.html', chunks: ['manifest', 'vendor', filename], inject: true, };
if (configs) { conf = merge(conf, configs) }
if (process.env.NODE_ENV === 'production') { conf = merge(conf, { minify: { removeComments: true, collapseWhitespace: true, }, chunksSortMode: 'manual' }) }
map[filename] = conf })
return map }
|
const utils = require('./build/utils')
module.exports = { pages: utils.setPages(), }
|
这样我们多页应用基于 pages
配置的改进就大功告成了,当你运行打包命令来查看输出结果的时候,你会发现和之前的方式相比并没有什么变化,
这就说明这两种方式都适用于多页的构建,但是这里还是推荐
大家使用更便捷的 pages
配置
多页面应用分页面打包
如果支持分项目编译打包到相应文件夹中,请看我的github 编译打包到相应文件夹
路由配置
首先我们要明确一点就是,多页应用中的每个单页都是相互隔离的,即如果你想从 page1
下的路由跳到 page2
下的路由,
你无法使用 vue-router
中的方法进行跳转,需要使用原生方法:location.href
或 location.replace
<template> <div id="app"> <div id="nav"> <a @click="goFn('')">Index</a> | <a @click="goFn('page1')">Page1</a> | <a @click="goFn('page2')">Page2</a> | </div> <router-view/> </div> </template>
<script> export default { methods: { goFn(name) { location.href = `${process.env.BASE_URL}` + name } } } </script>
|
但是为了保持和Vue
路由跳转同样的风格,我可以对单页之间的跳转做一下封装,
实现一个Navigator
类,类的代码可以查看本文最后的示例,封装完成后我们可以将跳转方法修改为
this.$openRouter({ name: name, // 跳转地址 query: { text: 'hello' // 可以进行参数传递 }, })
|
将其绑定到 Vue 的原型链上
import { Navigator } from '../../common' // 引入 Navigator
Vue.prototype.$openRouter = Navigator.openRouter; // 添加至 Vue 原型链
|
至此我们已经能够成功模仿 vue-router
进行单页间的跳转,但是需要注意的是因为其本质使用的是 location
跳转,所以必然会产生浏览器的刷新与重载
重定向
当我们完成上述路由跳转的功能后,可以在本地服务器上来进行一下测试,你会发现Index
首页可以正常打开,
但是跳转 Page1、Page2
却仍然处于 Index
父组件下,这是因为浏览器认为你所要跳转的页面还是在 Index
根路由下,
同时又没有匹配到Index
单页中对应的路由。这时候我们服务器需要做一次重定向,将下方路由指向对应的 html
文件即可
/vue/page1 -> /vue/page1.html /vue/page2 -> /vue/page2.html
|
在 vue.config.js
中,我们需要对 devServer
进行配置,添加 historyApiFallback
配置项,
该配置项主要用于解决 HTML5 History API
产生的问题,比如其 rewrites
选项用于重写路由
let baseUrl = '/vue/';
module.exports = { devServer: { historyApiFallback: { rewrites: [ { from: new RegExp(baseUrl + 'page1'), to: baseUrl + 'page1.html' }, { from: new RegExp(baseUrl + 'page2'), to: baseUrl + 'page2.html' }, ] } } }
|