開篇
前段時間,看到群裡一些小夥伴面試的時候被面試官問到這類題目。平時大家開發vue專案的時候,相信大部分人都是使用 vue-cli
腳手架生成的專案架構,然後
npm run install
安裝依賴,npm run serve
啟動專案然後就開始寫業務程式碼了。
但是對專案裡的webpack
封裝和配置瞭解的不清楚,容易導致出問題不知如何解決,或者不會通過webpack
去擴充套件新功能。
該篇文章主要是想告訴小夥伴們,如何一步一步的通過 webpack4
來搭建自己的vue
開發環境
首先我們要知道 vue-cli
生成的專案,幫我們配置好了哪些功能?
ES6
程式碼轉換成ES5
程式碼scss/sass/less/stylus
轉css
.vue
檔案轉換成js
檔案- 使用
jpg
、png
,font
等資原始檔 - 自動新增css各瀏覽器產商的字首
- 程式碼熱更新
- 資源預載入
- 每次構建程式碼清除之前生成的程式碼
- 定義環境變數
- 區分開發環境打包跟生產環境打包
- ....
1. 搭建 webpack
基本環境
該篇文章並不會細講 webpack
是什麼東西,如果還不是很清楚的話,可以先去看看 webpack官網
簡單的說,webpack
是一個模組打包機,可以分析你的專案依賴的模組以及一些瀏覽器不能直接執行的語言jsx
、vue
等轉換成 js
、css
檔案等,供瀏覽器使用。
1.1 初始化專案
在命令列中執行 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 目錄下生成了一個mian.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
轉 ES5
程式碼
- 安裝相關依賴
npm install babel-loader @babel/core @babel/preset-env
複製程式碼
- 修改
webpack.config.js
配置
- 在專案根目錄新增一個
babel.config.js
檔案
- 然後執行
npm run serve
命令,可以看到 ES6程式碼被轉成了ES5程式碼了
2.1.1 ES6/7/8 Api
轉es5
babel-loader
只會將 ES6/7/8語法轉換為ES5語法,但是對新api並不會轉換。
我們可以通過 babel-polyfill 對一些不支援新語法的客戶端提供新語法的實現
- 安裝
npm install @babel/polyfill
複製程式碼
- 修改
webpack.config.js
配置
在 entry
中新增 @babel-polyfill
2.2 配置 scss
轉 css
在沒配置 css
相關的 loader
時,引入scss
、css
相關檔案打包的話,會報錯
- 安裝相關依賴
npm install sass-loader dart-sass css-loader style-loader -D
複製程式碼
sass-loader
, dart-sass
主要是將 scss/sass 語法轉為css
css-loader
主要是解析 css 檔案
style-loader
主要是將 css 解析到 html
頁面 的 style
上
- 修改
webpack.config.js
配置
2.3 配置 postcss 實現自動新增css3字首
- 安裝相關依賴
npm install postcss-loader autoprefixer -D
複製程式碼
- 修改
webpack.config.js
配置
- 在專案根目錄下新建一個
postcss.config.js
2.3 使用 html-webpack-plugin
來建立html頁面
使用 html-webpack-plugin
來建立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 熱更新功能
通過程式碼的熱更新功能,我們可以實現不重新整理頁面的情況下,更新我們的頁面
- 安裝依賴
npm install webpack-dev-server -D
複製程式碼
- 修改
webpack.config.js
配置
通過配置 devServer
和 HotModuleReplacementPlugin
外掛來實現熱更新
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>
@import './assets/styles/var.scss';
.App {
color: $primary-color;
}
</style>
複製程式碼
- 修改
main.js
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App)
}).$mount('#app')
複製程式碼
- 執行一下
npm run serve
4. 定義環境變數
通過 webpack
提供的DefinePlugin
外掛,可以很方便的定義環境變數
plugins: [
new webpack.DefinePlugin({
'process.env': {
VUE_APP_BASE_URL: JSON.stringify('http://localhost:3000')
}
}),
]
複製程式碼
5. 區分生產環境和開發環境
新建兩個檔案
-
webpack.dev.js
開發環境使用 -
webpack.prod.js
生產環境使用 -
webpack.config.js
公用配置 -
開發環境與生產環境的不同
5.1 開發環境
- 不需要壓縮程式碼
- 需要熱更新
- css不需要提取到css檔案
- sourceMap
- ...
5.2 生產環境
- 壓縮程式碼
- 不需要熱更新
- 提取css,壓縮css檔案
- sourceMap
- 構建前清除上一次構建的內容
- ...
- 安裝所需依賴
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
使用者拷貝靜態資源
5.3 開發環境配置
- 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 生產環境配置
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 總結
到目前為止,我們已經成功的自己搭建了一個 vue
開發環境,不過還是有一些功能欠缺的,有興趣的小夥伴可以交流交流。在搭建過程中,還是會踩很多坑的。
如果還不熟悉 webpack 的話,建議自己搭建一次。可以讓自己能深入的理解 vue-cli
替我們做了什麼
推薦閱讀
歡迎關注
歡迎關注公眾號“碼上開發”,每天分享最新技術資訊