本文只講不使用vue-cli
徒手搭建vue3
項目,目的是了解日常開發(fā)常用模塊搭配酗捌,了解各個模塊的功能作用,以便自己可以搭建除vue
之外的(如rect
或原生開發(fā)的)項目。實際開發(fā)時短绸,如果使用到vue
或rect
,建議使用官方cli
創(chuàng)建項目筹裕,然后再安裝其他常用模塊會方便很多醋闭。
目標
- 打包壓縮
- 熱更新
- 編譯ES6+使兼容主流瀏覽器
- 安裝vue
- 支持編譯scss
- css分離打包
- 固定模塊單獨打包
- css3兼容處理
- 響應式單位處理
- 靜態(tài)資源處理
- 接口代理
- 多頁面開發(fā)
一、實現打包壓縮
- 運行
npm init -y
朝卒,此時在目錄下生成了package.json
文件 - 運行
npm install -D webpack webpack-cli
安裝webpack
证逻,版本如下:
"webpack": "^5.39.0",
"webpack-cli": "^4.7.2"
- 在項目目錄下創(chuàng)建目錄
src
,并在src
內創(chuàng)建index.js
文件
demo
|-- node_modules
|-- src
|-- index.js
|-- package.json
- 在
index.js
隨便寫一些代碼
async function fn(){
let n = await new Promise((r => {
setTimeout(() => {
r(1)
}, 1000);
}))
return n
}
fn().then(n => {
console.log(n)
})
- 在
package.json
文件內添加dev
和build
指令:
"scripts": {
"dev": "webpack --mode development",
"build": "webpack --mode production"
}
此時抗斤,運行npm run dev
或npm run build
即可正常打包囚企,如運行npm run build
,會生成打包壓縮后的文件dist/main.js
瑞眼,其代碼如下:
(async function(){return await new Promise((e=>{setTimeout((()=>{e(1)}),1e3)}))})().then((e=>{console.log(e)}));
可以看到代碼已被打包壓縮龙宏,因webpack4+
開始,內部使用了terser
壓縮工具(據說uglify-js
不支持ES6+
)伤疙。
PS:可以通過配置webpack.config.js
如下關閉壓縮
module.exports = {
optimization: false
}
當然银酗,大多數時候這是不必要的,但也會有它的使用場景徒像,比如使用webpack開發(fā)小程序黍特,由于development
模式webpack會使用到eval
,而小程序不支持eval
厨姚,而開發(fā)時又不希望編譯太慢(壓縮極影響編譯速度)衅澈,此時就可以使用production
模式然后關掉optimization
。
二谬墙、實現熱更新
- 運行
npm i -D webpack-dev-server html-webpack-plugin
今布,版本如下:
"html-webpack-plugin": "^5.3.1",
"webpack-dev-server": "^3.11.2"
- 在
package.json
添加start
指令:
"scripts": {
"start": "webpack serve --mode development --open",
"dev": "webpack --mode development",
"build": "webpack --mode production"
}
- 在項目目錄下添加
webpack.config.js
文件经备,代碼如下:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
target: 'web',
devServer: {
contentBase: './dist',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Demo'
}),
]
}
此時,運行npm start
部默,自動打開瀏覽器并打開地址http://localhost:8080
侵蒙。
三、實現編譯ES6+
從上方打包后的代碼看到傅蹂,并沒有編譯ES6+為ES5纷闺,所以需要使用babel
來編譯。
- 運行
npm i -D @babel/core @babel/preset-env babel-loader
份蝴,版本如下:
"@babel/core": "^7.14.6",
"@babel/preset-env": "^7.14.5",
"babel-loader": "^8.2.2",
- 在
webpack.config.js
中添加
module: {
...
rules: [{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}],
...
}
此時犁功,運行npm run build
后,打包的代碼雖然把let const async/await
等語法轉換了婚夫,但還不完全是ES5
代碼浸卦,比如Promise
對象。所以案糙,接下來就來解決polyfill
的問題限嫌。
- 運行
npm i --save core-js
,它就是把polyfill
拆分成小顆粒的包代碼庫时捌,以便babel-loader
動態(tài)的import
使用到的對象怒医。版本如下:
"core-js": "^3.14.0"
- 在
package.json
添加字段browserslist
關于browserslist
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"Android >= 4.4",
"iOS >=5"
]
這是移動端兼容目標瀏覽器的常用配置,不考慮IE
瀏覽器奢讨。
- 在
webpack.config.js
中修改babel-loader
的配置如下:
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
// 編譯必須排除core-js中的代碼稚叹,不然可能會發(fā)生錯誤
exclude: [
/node_modules[\\\/]core-js/,
/node_modules[\\\/]webpack[\\\/]buildin/,
],
presets: [['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3
}]]
}
}
}
此時運行npm run build
就可以看到控制臺動態(tài)的打包了兼容處理的代碼庫
PS: 如果覺得動態(tài)import
不靠譜,那么也可以選擇簡單粗暴直接安裝babel-polyfill
禽笑,然后直接import 'babel-polyfill'
- 運行
npm i --save whatwg-fetch
安裝whatwg-fetch
入录,這是對web
端的fetch
的兼容處理,使能夠在不同瀏覽器使用fetch
發(fā)送異步請求佳镜。這種單顆粒的直接在文件開頭import 'whatwg-fetch'
即可僚稿。
四、安裝vue 3.x
- 運行
npm i --save vue@next
安裝vue
- 運行
npm i -D vue-loader@next @vue/compiler-sfc
蟀伸,讓webpack
支持單文件組件(sfc
) - 版本號如下:
"@vue/compiler-sfc": "^3.1.1",
"vue-loader": "^16.2.0",
"vue": "^3.1.1",
- 在
webpack.config.js
配置VueLoaderPlugin
和vue-loader
蚀同。另外,規(guī)范一下文件名和目錄啊掏,把src
目錄下的index.js
文件更名為main.js
蠢络,在項目目錄下新增目錄public
并添加index.html
文件,修改wepback.config.js
的對應配置迟蜜。
目前為止刹孔,webpack.config.js
完整配置如下:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
target: 'web',
entry: {
app: './src/main.js'
},
devServer: {
contentBase: './dist',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Demo',
template: './public/index.html'
}),
new VueLoaderPlugin()
],
module: {
rules: [{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
exclude: [
/node_modules[\\\/]core-js/,
/node_modules[\\\/]webpack[\\\/]buildin/,
],
presets: [['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3
}]]
}
}
},{
test: /\.vue$/,
loader: 'vue-loader'
}]
}
}
public/index.html
文件代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Demo</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
- 在
src
目錄創(chuàng)建App.vue
文件,代碼如下:
<template>
<div>Hello Webpack!</div>
</template>
<script>
export default {
data(){}
}
</script>
<style>
</style>
- 將
src/main.js
的代碼修改如下:
import {createApp} from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
到此娜睛,vue3基本開發(fā)支持已正常完成髓霞,可以運行npm start
看看椿猎。
五蒲拉、使用支持編譯scss
- 運行
npm i -D style-loader css-loader sass-loader sass
,并在webpack.config.js
的module.rules
字段添加如下配置:
},{
test: /\.s[ac]ss$/i,
use: ['style-loader', 'css-loader', 'sass-loader']
}]
- 在
src/App.vue
文件修改style
標簽并寫簡單的樣式如下
<style lang="scss">
#app{
div{
color: red
}
}
</style>
運行npm start
不出意外的話吴裤,應該看到文字變紅了铺罢。
六酌住、分離css
目前配置運行npm run build
打包偶房,js
和css
是完全揉在一起打包進app.js
文件的听绳,我們希望把css
代碼從app.js
中分離為單獨的css
文件。
- 運行
npm i -D mini-css-extract-plugin
安裝插件邀层,版本如下:
"mini-css-extract-plugin": "^1.6.0",
- 修改
webpack.config.js
返敬,由于在development
模式下,為了快速編譯寥院,故不應該分離css
救赐,而應該只在production
模式使用這個插件。順便只磷,對不同開發(fā)模式做不同的其他配置項修改。完整配置如:
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const isProd = process.env.NODE_ENV === 'production'
const prodPlugins = []
if(isProd){
prodPlugins.push(new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
}))
}
module.exports = {
target: 'web',
mode: process.env.NODE_ENV,
entry: {
app: './src/main.js'
},
output: {
clean: isProd,
filename: isProd ? '[name].[contenthash].js' : '[name].js'
},
devServer: {
contentBase: './dist',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Demo',
template: './public/index.html'
}),
new VueLoaderPlugin(),
...prodPlugins
],
module: {
rules: [{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
exclude: [
/node_modules[\\\/]core-js/,
/node_modules[\\\/]webpack[\\\/]buildin/,
],
presets: [['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3
}]]
}
}
},{
test: /\.vue$/,
loader: 'vue-loader'
},{
test: /\.s[ac]ss$/i,
use: [isProd ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader', 'sass-loader']
},
]
}
}
上方代碼中泌绣,process.env.NODE_ENV
目前是undefined
的钮追,我們需要修改package.json
的指令,在執(zhí)行環(huán)境中加入NODE_ENV
變量阿迈。這里需要安裝cross-env
來處理跨平臺兼容元媚。
- 運行
npm i -D cross-env
,安裝版本如下:
"cross-env": "^7.0.3",
- 修改
package.json
指令如下:
"scripts": {
"start": "cross-env NODE_ENV=development webpack serve --open",
"dev": "cross-env NODE_ENV=development webpack",
"build": "cross-env NODE_ENV=production webpack"
},
這樣苗沧,當運行npm run build
時刊棕,就可以看到css
被分離了。
七待逞、固定模塊單獨打包
因為像vue
的源碼部分是固定不變的甥角,應該把它與頁面的邏輯代碼分離出去,這樣在項目版本跌代時识樱,vue
就可以來自緩存嗤无,而只需要加載邏輯代碼。
- 在
webpack.config.js
添加如下配置:
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all',
},
},
},
},
意思是把所有的來自node_modules
模塊都打包到vendor.js
中怜庸。
八当犯、css3兼容處理
- 運行
npm i -D postcss-loader postcss postcss-preset-env
,版本如下:
"postcss": "^8.3.5",
"postcss-loader": "^6.1.0",
"postcss-preset-env": "^6.7.0",
- 修改
webpack.config.js
中的module.rules
里的css-loader
后加入如下:
{
test: /\.s[ac]ss$/i,
use: [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env'
],
},
},
},
'sass-loader'
]
}
- 在
App.vue
中添加css3
代碼割疾,然后運行npm start
測試看看
<style lang="scss">
#app{
div{
color: purple;
display: inline-block;
animation: rotating 6s linear infinite;
}
}
@keyframes rotating{
from{
transform: rotate(0);
}
to{
transform: rotate(360deg);
}
}
</style>
可以看到
animation
被自動補上了-webkit-
嚎卫。
PS: postcss-preset-env
已經包含了autoprefixer
,所以不用再安裝autoprefixer
宏榕。postcss-preset-env
會根據package.json
的browserslist
字段來處理對應的兼容性拓诸。
九侵佃、響應式單位處理
一般有兩種,rem
和vw
恰响,這里由于是移動端項目趣钱,所以推薦vw
。設計稿的寬度標準是750px
胚宦。
- 運行
npm i -D postcss-plugin-pxtoviewport
首有,版本如下:
"postcss-plugin-pxtoviewport": "0.0.6",
- 在
webpack.config.js
的postcss-loader
加入插件
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env',
['postcss-plugin-pxtoviewport', {
viewportWidth: 750, // 設計稿寬
unitPrecision: 3, // 計算結果不整除時要保留的小數位數
viewportUnit: 'vw', // 使用單位vw
minPixelValue: 1, // 小于這個值時不處理
mediaQuery: true // 允許在媒體查詢中轉換`px`
}]
],
},
},
},
一般移動端頁面是根據頁面寬而高度自動等比縮放,所以枢劝,只需要所有尺寸都基于這個寬度即可井联。
- 修改
App.vue
樣式代碼如下:
...
div{
background: green;
height: 150px;
}
...
可以看到,
150px
被轉換成了20vw
您旁,其計算公式100*(150/750)
烙常,這樣,就可以放心的按設計稿尺寸去寫css
了鹤盒。
十蚕脏、靜態(tài)資源處理
接下來要處理的是圖片、音視頻侦锯、字體等的文件驼鞭。引入方式有兩種,一種是有.
開頭的路徑(如'./'
和'../'
)尺碰,需要url-loader file-loader raw-loader
等這類加載器挣棕,另一種是無.
開頭的路徑(如'/'
和''
),需要copy-webpack-plugin
拷貝亲桥,包含目錄拷貝洛心。
- 第一種,
webpack5
已經內置了資源加載模塊题篷,直接配置即可词身,無需安裝loader
。在webpack.config.js
的module.rules
數組中添加以下配置悼凑。
rules: [
...
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
type: 'asset'
},{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf)(\?.*)?$/,
type: 'asset/resource'
}]
type
說明:
asset
: 表示讓webpack
決定選擇data uri
(即base64)或uri
形式偿枕;
asset/resoure
: 表示使用uri
形式。
點擊了解更多參數
- 第二種户辫,運行
npm i -D copy-webpack-plugin
安裝插件渐夸,然后在webpack.config.js
頭入引入插件,然后在plugins
數組中添加如下:
const path = require('path')
const CopyWebpackPlugin = require('copy-webpack-plugin')
...
plugins:[
new CopyWebpackPlugin({
patterns: [{
from: path.resolve(__dirname, 'public'),
to: path.resolve(__dirname, 'dist'),
toType: 'dir',
filter: resourcePath => {
return !/\.html$/.test(resourcePath)
}
}]
})
]
上方配置是把public
目錄下的資源文件拷貝到dist
渔欢,并且忽略掉.html
文件墓塌。更多目錄可以繼續(xù)往patterns
數組中添加。
- 運行
npm start
測試一下,把圖片logo.png
放在public
中苫幢,然后在App.vue
中添加<img src="logo.png"/>
看看访诱,然后再改成<img src="./logo.png"/>
,此時會報錯找不到資源韩肝,需要把logo.png
移動到src
目錄触菜。可自行測試不同大小的圖片哀峻,看看各種情況生成的地址涡相。
十一、接口代理
接口代理其實很簡單剩蟀,但很多不熟悉的人經常調試不通催蝗,因為沒有真正理解,所以對不同的情況不知道怎么配置育特。
情況一:后端接口路徑都是同一個開頭丙号,如:/api/login, /api/user, /api/xxx
。這就好辦了缰冤,簡單配置如下即可犬缨。
devServer: {
contentBase: './dist',
proxy: {
'/api': {
target: 'http://localhost:3000',
secure: true
}
}
},
在頁面中發(fā)請求示例:
fetch('/api/login', {})
.then(res => res.json())
.then(res => {
console.log(res)
})
后端最終接收到的請求是來自地址http://localhost:3000/api/login
情況二:后端接口路徑開頭不同,如: /login, /user, /xxx
棉浸。這種有兩種配置可以選擇:
- 在頁面中開發(fā)時將所有接口都以特定路徑開頭遍尺,部署正式環(huán)境時再去掉,比如同樣使用
/api
開頭涮拗,則配置如下:
devServer: {
contentBase: './dist',
proxy: {
'/api': {
target: 'http://localhost:3000',
secure: true,
pathRewrite: {
'^/api': ''
}
}
}
},
在頁面中發(fā)請求示例:
fetch('/api/login', {}).then(res => res.json()).then(res => {
console.log(res)
})
后端最終接收到的請求是來自地址http://localhost:3000/login
,注意與情況一的區(qū)別迂苛,這里pathRewrite
的配置就是把頁面發(fā)請求時帶的/api
去掉三热,使后端真正得到的地址是http://localhost:3000/login
。
這種方式比較穩(wěn)三幻,不怕與頁面路徑有沖突就漾,但有一個缺點就是發(fā)布正式時要把頁面中的/api
去掉,因為到正式環(huán)境時接口和頁面地址是同一個域下念搬,不需要代理抑堡。一般處理方式是:
// api.js
const apiBase = /^http\:\/\/localhost/.test(window.location.href) ? '/api' : ''
export function request(path, params){
// TODO: build params
return fetch(apiBase + path, params).then(res => res.json())
}
這樣導出一個request
函數來專門發(fā)送請求
import {request} from './api'
request('/login', {}).then(res => {
console.log(res)
})
這樣,只要發(fā)布正式的地址不是http://localhost
訪問就正常朗徊。
接下來說另一種配置
- 保持地址原樣首妖,正式環(huán)境與開發(fā)環(huán)境一致,無需在頁面處理
devServer: {
contentBase: './dist',
proxy: [{
context: ['/login', '/user'],
target: 'http://localhost:3000',
secure: true
}]
},
在頁面中發(fā)請求示例:
fetch('/login', {}).then(res => res.json()).then(res => {
console.log(res)
})
后端最終接收到的請求地址還是http://localhost:3000/login
爷恳,這種配置雖然保持了開發(fā)環(huán)境還正式環(huán)境相同的頁面代碼有缆,但也有缺點,如果開頭不一樣接口很多,有十幾個或幾十個棚壁,那得在context
字段全部寫上杯矩,如此,單面應用history
模式或多頁面開發(fā)很容易沖突袖外,比如有一個用戶信息html
頁面的訪問地址是http://localhost:8080/user
史隆,而獲取用戶信息的接口是/user
且是GET
請求,那么訪問http://localhost:8080/user
時曼验,不會進入html
頁面泌射,而是被代理到了http://localhost:3000/user
。所以蚣驼,一般不推薦魄幕。
十二、多頁面開發(fā)配置
- 先把目錄結構改成如下:
兩個index.js
的代碼均如下:
import {createApp} from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
src/index/App.vue
代碼如:
<template>
<div class="index">index page</div>
</template>
<script>
export default {
data() {}
};
</script>
<style lang="scss"></style>
src/login/App.vue
代碼如:
<template>
<div class="login">login page</div>
</template>
<script>
export default {
data() {}
};
</script>
<style lang="scss"></style>
- 修改
webpack.config.js
以下幾個地方:
...
entry: {
index: './src/index/index.js',
login: './src/login/index.js'
},
...
plugins: [
new HtmlWebpackPlugin({
title: 'index',
template: './public/index.html',
filename: 'index.html',
chunks: ['index']
}),
new HtmlWebpackPlugin({
title: 'login',
template: './public/index.html',
filename: 'login.html',
chunks: ['login']
}),
...
]
運行npm start
就可看到頁面index page
颖杏,在瀏覽器輸入地址http://localhost:8080/login.html
就可以訪問login
頁面纯陨。
這就是最基本的多頁面配置。