開篇
前段時間殊橙,看到群里一些小伙伴面試的時候被面試官問到這類題目辐宾。平時大家開發(fā)vue項目的時候狱从,相信大部分人都是使用 vue-cli
腳手架生成的項目架構,然后
npm run install
安裝依賴叠纹,npm run serve
啟動項目然后就開始寫業(yè)務代碼了季研。
但是對項目里的webpack
封裝和配置了解的不清楚,容易導致出問題不知如何解決誉察,或者不會通過webpack
去擴展新功能与涡。
該篇文章主要是想告訴小伙伴們,如何一步一步的通過 webpack4
來搭建自己的vue
開發(fā)環(huán)境
首先我們要知道 vue-cli
生成的項目冒窍,幫我們配置好了哪些功能递沪?
-
ES6
代碼轉(zhuǎn)換成ES5
代碼 -
scss/sass/less/stylus
轉(zhuǎn)css
-
.vue
文件轉(zhuǎn)換成js
文件 - 使用
jpg
、png
综液,font
等資源文件 - 自動添加css各瀏覽器產(chǎn)商的前綴
- 代碼熱更新
- 資源預加載
- 每次構建代碼清除之前生成的代碼
- 定義環(huán)境變量
- 區(qū)分開發(fā)環(huán)境打包跟生產(chǎn)環(huán)境打包
- ....
1. 搭建 webpack
基本環(huán)境
該篇文章并不會細講 webpack
是什么東西款慨,如果還不是很清楚的話,可以先去看看 webpack官網(wǎng)
簡單的說谬莹,webpack
是一個模塊打包機檩奠,可以分析你的項目依賴的模塊以及一些瀏覽器不能直接運行的語言jsx
、vue
等轉(zhuǎn)換成 js
附帽、css
文件等埠戳,供瀏覽器使用。
1.1 初始化項目
在命令行中執(zhí)行 npm init
然后一路回車就行了蕉扮,主要是生成一些項目基本信息整胃。最后會生成一個 package.json
文件
npm init
1.2 安裝webpack
1.3 寫點小代碼測試一下webpack
是否安裝成功了
新建一個src
文件夾,然后再建一個main.js
文件
// src/main.js
console.log('hello webpack')
然后在 package.json 下面加一個腳本命令
然后運行該命令
npm run serve
如果在 dist 目錄下生成了一個main.js
文件喳钟,則表示webpack
工作正常
2. 開始配置功能
- 新建一個
build
文件夾屁使,用來存放webpack
配置相關的文件 - 在
build
文件夾下新建一個webpack.config.js
,配置webpack
的基本配置 - 修改
webpack.config.js
配置
- 修改
package.json
文件奔则,將之前添加的serve
修改為
"serve": "webpack ./src/main.js --config ./build/webpack.config.js"
2.1 配置 ES6/7/8
轉(zhuǎn) ES5
代碼
- 安裝相關依賴
npm install babel-loader @babel/core @babel/preset-env
- 修改
webpack.config.js
配置
- 在項目根目錄添加一個
babel.config.js
文件
- 然后執(zhí)行
npm run serve
命令蛮寂,可以看到 ES6代碼被轉(zhuǎn)成了ES5代碼了
2.1.1 ES6/7/8 Api
轉(zhuǎn)es5
babel-loader
只會將 ES6/7/8語法轉(zhuǎn)換為ES5語法,但是對新api并不會轉(zhuǎn)換易茬。
我們可以通過 babel-polyfill 對一些不支持新語法的客戶端提供新語法的實現(xiàn)
- 安裝
npm install @babel/polyfill
- 修改
webpack.config.js
配置
在 entry
中添加 @babel-polyfill
2.1.2 按需引入polyfill
2.1.2 和 2.1.1 只需要配置一個就行
修改時間 2019-05-05酬蹋、 來自評論區(qū) 兮漫天 的提醒
- 安裝相關依賴
npm install core-js@2 @babel/runtime-corejs2 -S
- 修改 babel-config.js
配置了按需引入 polyfill
后,用到es6
以上的函數(shù)抽莱,babel
會自動導入相關的polyfill
范抓,這樣能大大減少 打包編譯后的體積
2.2 配置 scss
轉(zhuǎn) css
在沒配置 css
相關的 loader
時,引入scss
食铐、css
相關文件打包的話匕垫,會報錯
- 安裝相關依賴
npm install sass-loader dart-sass css-loader style-loader -D
sass-loader
, dart-sass
主要是將 scss/sass 語法轉(zhuǎn)為css
css-loader
主要是解析 css 文件
style-loader
主要是將 css 解析到 html
頁面 的 style
上
- 修改
webpack.config.js
配置
2.3 配置 postcss 實現(xiàn)自動添加css3前綴
- 安裝相關依賴
npm install postcss-loader autoprefixer -D
- 修改
webpack.config.js
配置
- 在項目根目錄下新建一個
postcss.config.js
2.3 使用 html-webpack-plugin
來創(chuàng)建html頁面
使用 html-webpack-plugin
來創(chuàng)建html頁面,并自動引入打包生成的js
文件
- 安裝依賴
npm install html-webpack-plugin -D
- 新建一個 public/index.html 頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
- 修改
webpack-config.js
配置
2.4 配置 devServer 熱更新功能
通過代碼的熱更新功能璃岳,我們可以實現(xiàn)不刷新頁面的情況下年缎,更新我們的頁面
- 安裝依賴
npm install webpack-dev-server -D
- 修改
webpack.config.js
配置
通過配置 devServer
和 HotModuleReplacementPlugin
插件來實現(xiàn)熱更新
2.5 配置 webpack 打包 圖片、媒體铃慷、字體等文件
- 安裝依賴
npm install file-loader url-loader -D
file-loader
解析文件url单芜,并將文件復制到輸出的目錄中
url-loader
功能與 file-loader
類似,如果文件小于限制的大小犁柜。則會返回 base64
編碼洲鸠,否則使用 file-loader
將文件復制到輸出的目錄中
- 修改
webpack-config.js
配置
添加rules
配置,分別對 圖片馋缅,媒體扒腕,字體文件進行配置
// build/webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
module.exports = {
// 省略其它配置 ...
module: {
rules: [
// ...
{
test: /\.(jpe?g|png|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
}
}
]
},
]
},
plugins: [
// ...
]
}
3. 讓 webpack
識別 .vue
文件
- 安裝需要的依賴文件
npm install vue-loader vue-template-compiler cache-loader thread-loader -D
npm install vue -S
vue-loader
用于解析.vue
文件
vue-template-compiler
用于編譯模板
cache-loader
用于緩存loader
編譯的結果
thread-loader
使用 worker
池來運行loader
,每個 worker
都是一個 node.js
進程萤悴。
- 修改
webpack.config.js
配置
// build/webpack.config.js
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
// 指定打包模式
mode: 'development',
entry: {
// ...
},
output: {
// ...
},
devServer: {
// ...
},
resolve: {
alias: {
vue$: 'vue/dist/vue.runtime.esm.js'
},
},
module: {
rules: [
{
test: /\.vue$/,
use: [
{
loader: 'cache-loader'
},
{
loader: 'thread-loader'
},
{
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
},
}
}
]
},
{
test: /\.jsx?$/,
use: [
{
loader: 'cache-loader'
},
{
loader: 'thread-loader'
},
{
loader: 'babel-loader'
}
]
},
// ...
]
},
plugins: [
// ...
new VueLoaderPlugin()
]
}
- 測試一下
- 在 src 新建一個 App.vue
// src/App.vue
<template>
<div class="App">
Hello World
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {};
}
};
</script>
<style lang="scss" scoped>
.App {
color: skyblue;
}
</style>
- 修改
main.js
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App)
}).$mount('#app')
- 運行一下
npm run serve
4. 定義環(huán)境變量
通過 webpack
提供的DefinePlugin
插件瘾腰,可以很方便的定義環(huán)境變量
plugins: [
new webpack.DefinePlugin({
'process.env': {
VUE_APP_BASE_URL: JSON.stringify('http://localhost:3000')
}
}),
]
5. 區(qū)分生產(chǎn)環(huán)境和開發(fā)環(huán)境
新建兩個文件
webpack.dev.js
開發(fā)環(huán)境使用webpack.prod.js
生產(chǎn)環(huán)境使用webpack.config.js
公用配置開發(fā)環(huán)境與生產(chǎn)環(huán)境的不同
5.1 開發(fā)環(huán)境
- 不需要壓縮代碼
- 需要熱更新
- css不需要提取到css文件
- sourceMap
- ...
5.2 生產(chǎn)環(huán)境
- 壓縮代碼
- 不需要熱更新
- 提取css,壓縮css文件
- sourceMap
- 構建前清除上一次構建的內(nèi)容
- ...
- 安裝所需依賴
npm i @intervolga/optimize-cssnano-plugin mini-css-extract-plugin clean-webpack-plugin webpack-merge copy-webpack-plugin -D
-
@intervolga/optimize-cssnano-plugin
用于壓縮css代碼 -
mini-css-extract-plugin
用于提取css到文件中 -
clean-webpack-plugin
用于刪除上次構建的文件 -
webpack-merge
合并webpack
配置 -
copy-webpack-plugin
用戶拷貝靜態(tài)資源
5.3 開發(fā)環(huán)境配置
- build/webpack.dev.js
// build/webpack.dev.js
const merge = require('webpack-merge')
const webpackConfig = require('./webpack.config')
const webpack = require('webpack')
module.exports = merge(webpackConfig, {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
module: {
rules: [
{
test: /\.(scss|sass)$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
},
{
loader: 'postcss-loader'
}
]
},
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('development')
}
}),
]
})
- webpack.config.js
// build/webpack.config.js
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
entry: {
// 配置入口文件
main: path.resolve(__dirname, '../src/main.js')
},
output: {
// 配置打包文件輸出的目錄
path: path.resolve(__dirname, '../dist'),
// 生成的 js 文件名稱
filename: 'js/[name].[hash:8].js',
// 生成的 chunk 名稱
chunkFilename: 'js/[name].[hash:8].js',
// 資源引用的路徑
publicPath: '/'
},
devServer: {
hot: true,
port: 3000,
contentBase: './dist'
},
resolve: {
alias: {
vue$: 'vue/dist/vue.runtime.esm.js'
},
extensions: [
'.js',
'.vue'
]
},
module: {
rules: [
{
test: /\.vue$/,
use: [
{
loader: 'cache-loader'
},
{
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
},
}
}
]
},
{
test: /\.jsx?$/,
loader: 'babel-loader'
},
{
test: /\.(jpe?g|png|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
}
}
]
},
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html')
}),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
]
}
5.4 生產(chǎn)環(huán)境配置
const path = require('path')
const merge = require('webpack-merge')
const webpack = require('webpack')
const webpackConfig = require('./webpack.config')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssnanoPlugin = require('@intervolga/optimize-cssnano-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = merge(webpackConfig, {
mode: 'production',
devtool: '#source-map',
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
name: 'chunk-vendors',
test: /[\\\/]node_modules[\\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
}
},
module: {
rules: [
{
test: /\.(scss|sass)$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
},
{
loader: 'postcss-loader'
}
]
},
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: 'production'
}
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].css'
}),
new OptimizeCssnanoPlugin({
sourceMap: true,
cssnanoOptions: {
preset: [
'default',
{
mergeLonghand: false,
cssDeclarationSorter: false
}
]
}
}),
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist')
}
]),
new CleanWebpackPlugin()
]
})
5.5 修改package.json
"scripts": {
"serve": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
6 打包分析
有的時候覆履,我們需要看一下webpack打包完成后蹋盆,到底打包了什么東西,
這時候就需要用到這個模塊分析工具了 webpack-bundle-analyzer
- 安裝依賴
npm install --save-dev webpack-bundle-analyzer
- 修改
webpack-prod.js
配置硝全,在plugins
屬性中新增一個插件
在開發(fā)環(huán)境中栖雾,我們是沒必要進行模塊打包分析的,所以我們將插件配置在了生產(chǎn)環(huán)境的配置項中
- 運行打包命令
npm run build
執(zhí)行成功后會自動打開這個頁面
7. 集成 VueRouter
,Vuex
- 首先是安裝相關依賴
npm install vue-router vuex --save
7.1 集成 Vue-Router
- 新增視圖組件
在src
目錄下新增兩個視圖組件src/views/Home.vue
和src/views/About.vue
// src/views/Home.vue
<template>
<div class="Home">
<h2>Home</h2>
</div>
</template>
<script>
export default {
name: 'Home',
data() {
return {};
}
};
</script>
<style lang="scss" scoped>
</style>
About.vue
內(nèi)容跟 Home.vue
差不多伟众,將里面的 Home
換成 About
就OK了
- 新增路由配置文件
在 src
目錄下新增一個 router/index.js
文件
// src/router/index.js
import Vue from 'vue'
import VueRouter from "vue-router";
import Home from '../views/Home';
import About from '../views/About';
Vue.use(VueRouter)
export default new VueRouter({
mode: 'hash',
routes: [
{
path: '/Home',
component: Home
},
{
path: '/About',
component: About
},
{
path: '*',
redirect: '/Home'
}
]
})
- 修改
main.js
文件
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
new Vue({
router,
render: h => h(App)
}).$mount('#app')
- 修改
App.vue
組件
// App.vue
// 在 template 中添加
// src/App.vue
<template>
<div class="App">
Hello World
</div>
<div>
// router-link 組件 用來導航到哪個路由
<router-link to="/Home">go Home</router-link>
<router-link to="/About">go About</router-link>
</div>
<div>
// 用于展示匹配到的路由視圖組件
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {};
}
};
</script>
<style lang="scss" scoped>
.App {
color: skyblue;
}
</style>
運行 npm run serve
命令析藕,如沒配置錯誤,是可以看到點擊不同的路由凳厢,會切換到不同的路由視圖
7.2 配置路由懶加載
在沒配置路由懶加載的情況下账胧,我們的路由組件在打包的時候,都會打包到同一個js
文件去数初,當我們的視圖組件越來越多的時候找爱,就會導致這個 js
文件越來越大。然后就會導致請求這個文件的時間變長泡孩,最終影響用戶體驗
- 安裝依賴
npm install @babel/plugin-syntax-dynamic-import --save-dev
- 修改
babel.config.js
module.exports = {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage"
}
]
],
plugins: [
// 添加這個
'@babel/plugin-syntax-dynamic-import'
]
}
- 修改
router/index.js
路由配置文件
import Vue from 'vue'
import VueRouter from "vue-router";
Vue.use(VueRouter)
export default new VueRouter({
mode: 'hash',
routes: [
{
path: '/Home',
component: () => import(/* webpackChunkName: "Home" */ '../views/Home.vue')
// component: Home
},
{
path: '/About',
component: () => import(/* webpackChunkName: "About" */ '../views/About.vue')
// component: About
},
{
path: '*',
redirect: '/Home'
}
]
})
- 運行命令
npm run build
查看是否生成了Home...js
文件 和About...js
文件
7.3 集成 Vuex
- 在
src
目錄下新建一個store/index.js
文件
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
counter: 0
}
const actions = {
add: ({commit}) => {
return commit('add')
}
}
const mutations = {
add: (state) => {
state.counter++
}
}
const getters = {
getCounter (state) {
return state.counter
}
}
export default new Vuex.Store({
state,
actions,
mutations,
getters
})
- 修改
main.js
文件 導入vuex
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store' // ++
new Vue({
router,
store, // ++
render: h => h(App)
}).$mount('#app')
- 修改
App.vue
车摄,查看 vuex 配置效果
// App.vue
<template>
<div class="App">
<div>
<router-link to="/Home">go Home</router-link>
<router-link to="/About">go About</router-link>
</div>
<div>
<p>{{getCounter}}</p>
<button @click="add">add</button>
</div>
<div>
<router-view></router-view>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'App',
data() {
return {};
},
computed: {
...mapGetters(['getCounter'])
},
methods: {
...mapActions(['add'])
}
};
</script>
<style lang="scss" scoped>
.App {
text-align: center;
color: skyblue;
font-size: 28px;
}
</style>
- 運行命令
npm run serve
當點擊按鈕的時候,可以看到我們的getCounter
一直在增加
8 總結
到目前為止仑鸥,我們已經(jīng)成功的自己搭建了一個 vue
開發(fā)環(huán)境吮播,不過還是有一些功能欠缺的,有興趣的小伙伴可以交流交流眼俊。在搭建過程中意狠,還是會踩很多坑的。
如果還不熟悉 webpack 的話疮胖,建議自己搭建一次环戈∶瓢澹可以讓自己能深入的理解 vue-cli
替我們做了什么
推薦閱讀
歡迎關注
歡迎關注公眾號“碼上開發(fā)”,每天分享最新技術資訊