Vite3遷移Webpack5的實(shí)現(xiàn)
為什么要做遷移
現(xiàn)有問題
1、按需加載頁(yè)面的時(shí)候加載速度慢。
2、熱更新時(shí)常失效,手動(dòng)刷新加載速度也慢。
性能提升
1、舊框架啟動(dòng)約13秒,但啟動(dòng)后每個(gè)頁(yè)面切換加載都得等待5-10s,開發(fā)體驗(yàn)較差。新框架項(xiàng)目啟動(dòng)加所有頁(yè)面加載約65秒。利用webpack5緩存的新特性,啟動(dòng)速度變快的同時(shí),帶來(lái)更好的開發(fā)體驗(yàn)。
2、舊框架在jenkins打包需要2分52秒,新框架打包僅需1分48秒,速度提升了37%。
webpack5為什么快
webpack5 較于 webpack4,新增了持久化緩存、改進(jìn)緩存算法等優(yōu)化,通過配置webpack 持久化緩存,來(lái)緩存生成的 webpack 模塊和 chunk,改善下一次打包的構(gòu)建速度,可提速 90% 左右
安裝依賴
"vue": "^3.2.37",
"webpack": "5.64.4",
"webpack-bundle-analyzer": "4.5.0",
"webpack-cli": "4.10.0",
"webpack-dev-server": "4.6.0",
"webpack-merge": "5.8.0"
"babel-loader": "8.2.3",
"@babel/plugin-transform-runtime": "7.16.4",
"clean-webpack-plugin": "4.0.0",
"css-loader": "6.5.1",
"css-minimizer-webpack-plugin": "3.2.0",// 對(duì)CSS文件進(jìn)行壓縮
"mini-css-extract-plugin": "2.4.5",// 將CSS文件抽取出來(lái)配置, 防止將樣式打包在 js 中文件過大和因?yàn)槲募缶W(wǎng)絡(luò)請(qǐng)求超時(shí)的情況。
"postcss-loader": "6.2.1",
"postcss-preset-env": "7.0.1",
"vue-style-loader": "4.1.3",
"style-loader": "^3.3.2",
"less-loader": "^11.1.0",
"friendly-errors-webpack-plugin": "1.7.0",
"html-webpack-plugin": "5.5.0",
"progress-bar-webpack-plugin": "2.1.0",
"vue-loader": "^17.0.1",
"eslint-webpack-plugin": "^4.0.0",
"stylelint-webpack-plugin": "^4.1.0",
"copy-webpack-plugin": "^11.0.0",
"cross-env": "^7.0.3",
"@babel/runtime-corejs3": "7.16.3"http:// 安裝到dependencieswebpack5配置
為了區(qū)分開發(fā)環(huán)境和打包環(huán)境,分了3個(gè)js文件(base、dev、prod),通過webpack-merge這個(gè)插件,進(jìn)行合并操作。
webpack.base.conf.js
const { resolve, babelLoaderConf } = require('./utils.js')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader/dist/index')
// const StylelintPlugin = require('stylelint-webpack-plugin')
const ESLintPlugin = require('eslint-webpack-plugin')
const { YouibotPlusResolver } = require('youibot-plus')
const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 將CSS文件抽取出來(lái)配置, 防止將樣式打包在 js 中文件過大和因?yàn)槲募缶W(wǎng)絡(luò)請(qǐng)求超時(shí)的情況。
const { AntDesignVueResolver } = require('unplugin-vue-components/resolvers')
const isDev = process.env.NODE_ENV === 'development' // 是否是開發(fā)模式
module.exports = {
entry: {
app: resolve('src/main.ts')
},
resolve: {
//引入模塊時(shí)不帶擴(kuò)展名,會(huì)按照配置的數(shù)組從左到右的順序去嘗試解析模塊
extensions: ['.ts', '.tsx', '.js', '.vue', '.json'],
alias: {
'@': resolve('src')
}
},
module: {
noParse: /^(vue|vue-router|youibot-plus|echarts)$/, // 不解析庫(kù)
rules: [
{
test: /\.vue$/,
use: [
{
loader: 'vue-loader'
}
],
include: /(src)/
},
//babel7之后已經(jīng)有了解析 typescript 的能力,也就不再需要 ts-loader
{
test: /\.(ts|js)x?$/,
use: [
{
loader: 'thread-loader', // 開啟多進(jìn)程打包
options: {
worker: 3
}
},
babelLoaderConf
],
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
// 開發(fā)環(huán)境使用style-looader(通過動(dòng)態(tài)添加 style 標(biāo)簽的方式,將樣式引入頁(yè)面),打包模式抽離css
isDev ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: false
}
}
]
},
{
test: /\.less$/,
use: [
isDev ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: false
}
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
// postcss-preset-env 內(nèi)部集成了 autoprefixer 添加css第三方前綴
plugins: ['postcss-preset-env']
}
}
},
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true
},
additionalData: '@import "@/styles/variables.less";'
}
}
]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
type: 'asset',
parser: {
dataUrlCondition: {
// 文件小于 10k 會(huì)轉(zhuǎn)換為 base64,大于則拷貝文件
maxSize: 10 * 1024
}
},
generator: {
filename: 'images/[base]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
type: 'asset',
generator: {
filename: 'files/[base]'
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
type: 'asset',
generator: {
filename: 'media/[base]'
}
}
]
},
plugins: [
new VueLoaderPlugin(),
//將打包后的文件自動(dòng)引入到index.html里面
new HtmlWebpackPlugin({
template: resolve('public/index.html'),
favicon: resolve('public/logo.ico')
}),
require('unplugin-vue-components/webpack')({
resolvers: [
AntDesignVueResolver({
importStyle: false
}),
YouibotPlusResolver()
],
dts: true,
dirs: ['src/components', 'src/pages'] // 按需加載的文件夾
}),
require('unplugin-auto-import/webpack')({
imports: ['vue', 'vue-router', 'pinia'],
resolvers: [AntDesignVueResolver()],
eslintrc: {
enabled: true,
filepath: '../.eslintrc-auto-import.json',
globalsPropValue: true
},
dts: 'src/types/auto-imports.d.ts'
}),
// new StylelintPlugin(),
new ESLintPlugin()
]
}webpack.dev.js
const { merge } = require('webpack-merge')
const webpack = require('webpack')
const { getNetworkIp, resolve } = require('./utils.js')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const common = require('./webpack.base.conf')
const chalk = require('chalk')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const devWebpackConfig = merge(common, {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
output: {
path: resolve('dist'),
filename: 'js/[name].[hash].js',
chunkFilename: 'js/[name].[hash].js',
publicPath: '/'
},
// 日志打印只打印錯(cuò)誤和警告
stats: 'errors-warnings',
devServer: {
host: getNetworkIp(),
port: 8094, // 端口號(hào)
open: true, // 自動(dòng)打開
hot: true, // 熱更新
allowedHosts: 'all',
client: {
progress: false, // 將運(yùn)行進(jìn)度輸出到控制臺(tái)。
overlay: { warnings: false, errors: true } // 全屏顯示錯(cuò)誤信息
}
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),
__VUE_OPTIONS_API__: true, //控制臺(tái)警告處理
__VUE_PROD_DEVTOOLS__: true //控制臺(tái)警告處理
})
],
// 緩存
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename] // 針對(duì)構(gòu)建的額外代碼依賴的數(shù)組對(duì)象。webpack 將使用這些項(xiàng)和所有依賴項(xiàng)的哈希值來(lái)使文件系統(tǒng)緩存失效。
},
cacheDirectory: resolve('temp_cache'),
name: 'scf-cache', // 路徑temp_cache/scf-cache
compression: 'gzip'
}
})
devWebpackConfig.plugins.push(
// 進(jìn)度條
new ProgressBarPlugin({
format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`,
clear: true
}),
// 錯(cuò)誤提示
new FriendlyErrorsWebpackPlugin({
// 成功的時(shí)候輸出
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${devWebpackConfig.devServer.port}`]
},
// 是否每次都清空控制臺(tái)
clearConsole: true
})
)
module.exports = devWebpackConfigwebpack.prod.js
const path = require('path')
const { merge } = require('webpack-merge')
const webpack = require('webpack')
const { resolve } = require('./utils.js')
const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 將CSS文件抽取出來(lái)配置, 防止將樣式打包在 js 中文件過大和因?yàn)槲募缶W(wǎng)絡(luò)請(qǐng)求超時(shí)的情況。
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') // 對(duì)CSS文件進(jìn)行壓縮
const TerserPlugin = require('terser-webpack-plugin')
const common = require('./webpack.base.conf')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyPlugin = require('copy-webpack-plugin')
module.exports = () => {
const analyzerPlugins = process.env.NODE_ENV === 'analyzer' ? [new BundleAnalyzerPlugin()] : []
return merge(common, {
mode: 'production',
optimization: {
// chunk拆分,提升首屏加載速度
splitChunks: {
cacheGroups: {
vendors: {
// 提取node_modules代碼
test: /node_modules/, // 只匹配node_modules里面的模塊
name: 'vendors', // 提取文件命名為vendors,js后綴和chunkhash會(huì)自動(dòng)加
minChunks: 1, // 只要使用一次就提取出來(lái)
chunks: 'initial', // 只提取初始化就能獲取到的模塊,不管異步的
minSize: 0, // 提取代碼體積大于0就提取出來(lái)
priority: 1 // 提取優(yōu)先級(jí)為1
},
commons: {
// 提取頁(yè)面公共代碼
name: 'commons', // 提取文件命名為commons
minChunks: 2, // 只要使用兩次就提取出來(lái)
chunks: 'initial', // 只提取初始化就能獲取到的模塊,不管異步的
minSize: 0 // 提取代碼體積大于0就提取出來(lái)
}
}
},
// 壓縮
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true, // 開啟多線程壓縮
terserOptions: {
compress: {
pure_funcs: ['console.log'] // 刪除console.log
}
}
}),
new CssMinimizerPlugin()
],
// tree shaking
usedExports: true
},
performance: {
hints: false
},
// devtool: 'source-map', //如果配置source-map的話,生產(chǎn)環(huán)境下也可以定位到具體代碼,但是相應(yīng)的打包文件也會(huì)變大(map文件體積,4m變成17m),而且會(huì)有代碼暴露的風(fēng)險(xiǎn)。
plugins: [
// 清空dist
new CleanWebpackPlugin(),
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'), // 復(fù)制public下文件
to: path.resolve(__dirname, '../dist'), // 復(fù)制到dist目錄中
filter: source => !source.includes('index.html') // 忽略index.html
}
]
}),
// css抽離
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash].css',
chunkFilename: 'css/[name].[contenthash].css'
}),
// css壓縮
new CssMinimizerPlugin(),
new webpack.DefinePlugin({
//在業(yè)務(wù)代碼中也可以訪問process變量區(qū)分環(huán)境
'process.env.NODE_ENV': JSON.stringify('production'),
__VUE_OPTIONS_API__: true, //控制臺(tái)警告處理
__VUE_PROD_DEVTOOLS__: false //控制臺(tái)警告處理
}),
...analyzerPlugins
],
output: {
path: resolve('dist'),
filename: 'js/[name].[hash].js',
chunkFilename: 'js/[name].[hash].js'
}
})
}utils.js
const path = require('path')
const os = require('os')
exports.getNetworkIp = function () {
let needHost = '' // 打開的host
try {
// 獲得網(wǎng)絡(luò)接口列表
let network = os.networkInterfaces()
for (let dev in network) {
let iface = network[dev]
for (let i = 0; i < iface.length; i++) {
let alias = iface[i]
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
needHost = alias.address
}
}
}
} catch (e) {
needHost = 'localhost'
}
return needHost
}
exports.resolve = function (dir) {
return path.join(__dirname, '..', dir)
}
// babel-loader配置
exports.babelLoaderConf = {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
targets: {
browsers: ['ie>=8', 'chrome>=62'],
node: '8.9.0'
},
debug: false,
useBuiltIns: 'usage',
corejs: '3.0'
}
],
[
'@babel/preset-typescript',
{
allExtensions: true // 支持所有文件擴(kuò)展名,否則在vue文件中使用ts會(huì)報(bào)錯(cuò)
}
]
],
plugins: [
[
//js文件在babel轉(zhuǎn)碼后會(huì)生成很多helper函數(shù)(可能有大量重復(fù)),polyfill會(huì)在全局變量上掛載目標(biāo)瀏覽器缺失的功能,這個(gè)插件的作用是將 helper 和 polyfill 都改為從一個(gè)統(tǒng)一的地方引入,并且引入的對(duì)象和全局變量是完全隔離的
//Babel 默認(rèn)只轉(zhuǎn)換新的 JavaScript 句法(syntax),而不轉(zhuǎn)換新的 API(polify實(shí)現(xiàn))
'@babel/plugin-transform-runtime',
{
corejs: 3
}
]
]
}
}知識(shí)點(diǎn)
環(huán)境區(qū)分
// package.json 命令行 "build:dev": "cross-env NODE_ENV=development webpack serve --config build/webpack.dev.js", "build:prod": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js", "build:analyzer": "cross-env NODE_ENV=analyzer webpack serve --config build/webpack.prod.js",
在window環(huán)境下需要cross-env這個(gè)依賴幫助我們node環(huán)境下做變量標(biāo)識(shí),通過NODE_ENV進(jìn)行聲明即可。
//webpack.dev.js
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),
__VUE_OPTIONS_API__: true, //控制臺(tái)警告處理
__VUE_PROD_DEVTOOLS__: true //控制臺(tái)警告處理
})
//webpack.prod.js
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
__VUE_OPTIONS_API__: true, //控制臺(tái)警告處理
__VUE_PROD_DEVTOOLS__: false //控制臺(tái)警告處理
}),在代碼中,通過definePlugin定義變量后,通過process.env.NODE_ENV來(lái)獲取當(dāng)前是開發(fā)環(huán)境還是生產(chǎn)環(huán)境。
css-loader和style-loader
css-loader的作用是將css文件轉(zhuǎn)換成webpack能夠處理的資源,而style-loader就是幫我們直接將css-loader解析后的內(nèi)容掛載到html頁(yè)面當(dāng)中
asset資源模塊
webpack5 新增資源模塊(asset module),允許使用資源文件(字體,圖標(biāo)等)而無(wú)需配置額外的 loader。
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
type: 'asset',
parser: {
dataUrlCondition: {
// 文件小于 10k 會(huì)轉(zhuǎn)換為 base64,大于則拷貝文件
maxSize: 10 * 1024
}
},
generator: {
filename: 'images/[base]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
type: 'asset',
generator: {
filename: 'files/[base]'
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
type: 'asset',
generator: {
filename: 'media/[base]'
}
}性能優(yōu)化
按需引入
echarts打包后占用體積過大
import * as echarts from 'echarts'//全局引入echarts
//按需引入echarts
import * as echarts from 'echarts/core'
import { BarChart } from 'echarts/charts'
import { LegendComponent, TitleComponent, TooltipComponent, GridComponent, DatasetComponent, TransformComponent } from 'echarts/components'
import { LabelLayout, UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'
import { setTimeSecond, setTimeStr } from '@/utils/index'
import useStore from '@/stores'
// 注冊(cè)必須的組件
echarts.use([
LegendComponent,
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
BarChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
])

可以看到echarts如果是全局引用的情況下,打包體積有3.67m,但按需引入后就只有1.36m了。
組件庫(kù)的按需引入
通過unplugin-vue-components/webpack插件,不會(huì)全局引入ant-design,會(huì)按需引入。
require('unplugin-vue-components/webpack')({
resolvers: [
AntDesignVueResolver({
importStyle: false
}),
YouibotPlusResolver()
],
dts: true,
dirs: ['src/components', 'src/pages'] // 按需加載的文件夾
}),分包策略
// chunk拆分,提升首屏加載速度
splitChunks: {
cacheGroups: {
vendors: {
// 提取node_modules代碼
test: /node_modules/, // 只匹配node_modules里面的模塊
name: 'vendors', // 提取文件命名為vendors,js后綴和chunkhash會(huì)自動(dòng)加
minChunks: 1, // 只要使用一次就提取出來(lái)
chunks: 'initial', // 只提取初始化就能獲取到的模塊,不管異步的
minSize: 0, // 提取代碼體積大于0就提取出來(lái)
priority: 1 // 提取優(yōu)先級(jí)為1
},
commons: {
// 提取頁(yè)面公共代碼
name: 'commons', // 提取文件命名為commons
minChunks: 2, // 只要使用兩次就提取出來(lái)
chunks: 'initial', // 只提取初始化就能獲取到的模塊,不管異步的
minSize: 0 // 提取代碼體積大于0就提取出來(lái)
}
}
},
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.ico" rel="external nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<script src="/config.js"></script>
<title>YOUIFLEET</title>
<link rel="icon" href="logo.ico" rel="external nofollow" />
<script defer="defer" src="js/vendors.cb5671c1aeb89321634e.js"></script>
<script defer="defer" src="js/app.cb5671c1aeb89321634e.js"></script>
<link href="css/vendors.acd8e0885f2241c62cf1.css" rel="external nofollow" rel="stylesheet" />
<link href="css/app.63706e02f684f71c27bd.css" rel="external nofollow" rel="stylesheet" />
</head>
<body>
<div id="app"></div>
<link rel="stylesheet/less" href="/color.less" rel="external nofollow" />
<script src="/less-2.7.2.min.js"></script>
</body>
</html>這里可以看到通過分包策略后,打出了兩個(gè)js文件,可以看到是defer異步執(zhí)行,不阻塞html的渲染(async也是異步的,但是并行加載,js加載好了就會(huì)執(zhí)行,如果js前后有依賴性,可能會(huì)出錯(cuò))。
多線程打包
{
test: /\.(ts|js)x?$/,
use: [
{
loader: 'thread-loader', // 開啟多進(jìn)程打包
options: {
worker: 3
}
},
babelLoaderConf
],
exclude: /node_modules/
},由于運(yùn)行在 Node.js 之上的 webpack 是單線程模型的,我們需要 webpack 能同一時(shí)間處理多個(gè)任務(wù),發(fā)揮多核 CPU 電腦的威力。
thread-loader 就能實(shí)現(xiàn)多線程打包,它把任務(wù)分解給多個(gè)子進(jìn)程去并發(fā)的執(zhí)行,子進(jìn)程處理完后再把結(jié)果發(fā)送給主進(jìn)程,來(lái)提升打包速度。
優(yōu)化策略遠(yuǎn)不止這幾項(xiàng),還有路由懶加載,組件懶加載,gzip壓縮,cdn引入第三方依賴,DllPlugin 動(dòng)態(tài)鏈接庫(kù),Web Worker,骨架屏...通過打包后的結(jié)果進(jìn)行對(duì)應(yīng)分析即可。
踩坑記錄
Stylelint報(bào)錯(cuò)

該問題需要通過husky配置lint-staged處理,但由于我們項(xiàng)目前后端代碼放在一個(gè)大文件夾下內(nèi)分單獨(dú)文件夾管理,配置不了husky,所以只能暫時(shí)將stylelint-webpack-plugin給注釋掉,如果大佬有解決方案可以在評(píng)論區(qū)提一下感謝。
Vue動(dòng)態(tài)路由配置component
// 生成路由數(shù)據(jù)
const generateRoute = (list: Array<IRouteData>): RouterType[] => {
const routerList: RouterType[] = []
const modules = require.context('../pages', true, /\.vue$/).keys()
/**
*
* @param { Array<IRouteData>} routers 接口返回?cái)?shù)據(jù)
* @param {RouterType[]} routerData 生成數(shù)據(jù)存儲(chǔ)
*/
function generateRouter(routers: Array<IRouteData>, routerData: RouterType[] = []): void {
routers.forEach(routerItem => {
const { url, name, icon, children } = routerItem
//判斷是否存在子路由
const isRouteChildren = children && children.length && children[0].type === 0
const redirect = isRouteChildren ? children[0].url : undefined
const component =
modules.indexOf(`.${url}/index.vue`) !== -1 ? () => import(/* webpackChunkName: "[request]" */ `@/pages${url}/index.vue`) : null
const routerItemData: RouterType = {
path: url,
redirect,
name,
component,
meta: {
title: name,
icon: icon,
attribution: name
},
children: []
}
if (isRouteChildren) {
generateRouter(children, routerItemData.children)
}
routerData.push(routerItemData)
})
}
generateRouter(list, routerList)
return routerList
}這個(gè)component配置包含了血淚史,因?yàn)橹耙婚_始component配置的時(shí)候找不到父路由的時(shí)候,我給配了子路由的component,導(dǎo)致后面component加載重復(fù)一直切換報(bào)錯(cuò),其實(shí)配置一個(gè)null就可以。
附錄
感謝以下大佬的文章分享
# 透過分析 webpack 面試題,構(gòu)建 webpack5.x 知識(shí)體系
# 前端性能優(yōu)化——包體積壓縮82%、打包速度提升65%
# 前端性能優(yōu)化——首頁(yè)資源壓縮63%、白屏?xí)r間縮短86%
#【腳手架】從0到1搭建React18+TS4.x+Webpack5項(xiàng)目(一)項(xiàng)目初始化
到此這篇關(guān)于Vite3遷移Webpack5的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Vite3遷移Webpack5內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue中實(shí)現(xiàn)滾動(dòng)加載與無(wú)限滾動(dòng)
本文主要介紹了Vue中實(shí)現(xiàn)滾動(dòng)加載與無(wú)限滾動(dòng),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
淺談vue項(xiàng)目4rs vue-router上線后history模式遇到的坑
今天小編就為大家分享一篇淺談vue項(xiàng)目4rs vue-router上線后history模式遇到的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2018-09-09
vue 請(qǐng)求后臺(tái)數(shù)據(jù)的實(shí)例代碼
本篇文章主要介紹了vue 請(qǐng)求后臺(tái)數(shù)據(jù)的實(shí)例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧、2017-06-06

