最近感覺自己越來越像一個API呼叫程式設計師,很多基礎的原理以及專案構建都沒實際操作過,所以這裡動手自己去搭建了一個vue專案,從webpack配置到vue配置,以及構建的優化,雖然寫得並不好,但是自己在這個過程中也學到了一些東西,以此記錄。
由於是真的從零開始,所以長文預警!!!?
初始化專案
首先☝️,在命令列中建立資料夾並進入,使用npm命令初始化專案:
mkdir vue-starter && cd vue-starter
npm init
複製程式碼
然後,建立index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue Starter</title>
</head>
<body>
<h1>First Step!</h1>
</body>
</html>
複製程式碼
建立src資料夾,並在src檔案中建立main.js檔案:
mkdir src && cd src
touch main.js
複製程式碼
src/main.js中寫入:
window.onload = () => {
console.log('load');
};
複製程式碼
這個時候如果在index.html中引入./src/main.js,並在瀏覽器中執行index.html會發現控制檯中列印了‘load’。程式碼少看起來並不複雜,但是當我們業務變複雜,之後程式碼量過大,就需要我們進行打包構建了?。
所以下面進行webpack配置。
webpack第一步
首先☝️,安裝webpack 和webpack-cli:
npm i -D webpack webpack-cli
複製程式碼
然後,在package.json中配置執行webpack的指令碼命令:
"scripts": {
"build": "cross-env NODE_ENV=production webpack --mode=production --config webpack.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
複製程式碼
這裡用了新依賴,cross-env,進行安裝:
npm i -D cross-env
複製程式碼
關於build指令碼命令解釋:
- cross-env依賴用於跨平臺設定環境變數。在多數情況下,在windows平臺下使用類似於: NODE_ENV=production的命令列指令會卡住;windows平臺與POSIX在使用命令列時有許多區別(POSIX,使用$ENV_VAR;windows使用%ENV_VAR%)。cross-env就是解決這類跨平臺問題的,統一每個平臺的命令。
- NODE_ENV=development 設定 NODE 的環境變數為開發環境
- --mode=procution配置為生產環境模式
- --config webpack.config.js 指明配置檔案位置(相對路徑)
建立webpack.config.js,webpack配置如下:
const path = require('path');
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist/')
}
};
module.exports = config;
複製程式碼
上面是最基本的webpack配置,大概意思就是根據entry的./src/main.js內容進行打包構建輸出到bundle.js.
然後執行npm run build命令會,會得到dist資料夾以及dist/bundle.js檔案,dist/bundle.js就是src/main.js打包構建之後的內容。
現在將index.html中的src/main.js改成dist/bundle.js。
<script src="./dist/bundle.js"></script>
複製程式碼
再在瀏覽器中開啟index.html,可以看到也得到了相同的效果。
生成的bundle檔案新增hash
為什麼新增hash?
是為了防止瀏覽器快取機制阻止檔案的更新,為打包檔案新增hash字尾之後,每次構建打包生成的檔名的hash都會發生改變,強制瀏覽器進行重新整理,獲取當前最新的檔案就可以防止使用快取檔案。
如何新增? 在output中設定:
output: {
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, './dist/'),
}
複製程式碼
設定好hash之後,執行npm run build命令會發現dist下生成的bundle帶上了hash。
自動生成html檔案
在npm run build之後,dist資料夾中並沒有index.html檔案,要想引用打包的檔案,需要手動引用,並且由於上一步為bundle新增了hash,所以每次構建都需要手動修改script標籤的src路徑。
使用HtmlWebpackPlugin可以自動生成html檔案並注入打包的檔案。
安裝包:
npm i -D html-webpack-plugin
複製程式碼
在webpack.config.js中引入並在plugins中新增配置:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const htmlPlugin = new HtmlWebpackPlugin({
// 生成的html的title
title: 'Vue Starter',
// 生成的html的檔名
filename: 'index.html',
// 注入bundle到body中
inject: 'body'
});
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, './dist/')
},
plugins: [
htmlPlugin
]
};
module.exports = config;
複製程式碼
現在執行npm run build可以看到生成了index.html並且自動引用了帶hash字尾的bundle.[hash].js。
引入vue
首先,安裝vue與vue-loader:
npm i -D vue vue-loader
複製程式碼
安裝成功之後會看到控制檯有一個warning❗️:
npm WARN vue-loader@15.6.2 requires a peer of css-loader@* but none is installed. You must install peer dependencies yourself.
複製程式碼
意思是安裝的包還需要依賴css-loader,所以繼續安裝css-loader:
npm i -D css-loader
複製程式碼
然後在webpack.config.js中新增vue相關的loader:
const path = require('path');
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist/')
},
module: {
rules: [
{
test: /\.vue$/,
loader:'vue-loader'
}
]
}
};
module.exports = config;
複製程式碼
src下建立App.vue:
<template>
<div>
Second Step!
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
複製程式碼
src下建立main.js:
import Vue from 'vue';
import App from './App.vue';
const root = document.createElement('div')
document.body.appendChild(root)
new Vue({
render: (h) => h(App)
}).$mount(root)
複製程式碼
這時候我們執行npm run build命令會得到以下報錯❗️:
Module Error (from ./node_modules/vue-loader/lib/index.js):
[vue-loader] vue-template-compiler must be installed as a peer dependency, or a compatible compiler implementation must be passed via options.
複製程式碼
提示vue-loader需要依賴vue-template-compiler包,安裝該包即可:
npm i -D vue-template-compiler
複製程式碼
然後執行npm run build又報錯❗️啦:
ERROR in ./src/App.vue
Module Error (from ./node_modules/vue-loader/lib/index.js):
vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.
@ ./src/main.js 2:0-28 8:21-24
ERROR in ./src/App.vue?vue&type=template&id=4fa9bc52& 2:0
Module parse failed: Unexpected token (2:0)
You may need an appropriate loader to handle this file type.
複製程式碼
這個bug是因為vue-loader版本問題引起的,v15版本需要依賴VueloaderPlugin包,解決這個問題的辦法是使用v14版本的vue-loader,在package.json中修改vue-loader的版本為14.2.2,然後npm install一下就可以了。具體的解決辦法參考?enableVueLoader does not include VueLoaderPlugin?
然後再執行npm run build,可以看到生成了dist資料夾與dist資料夾下的bundle.js。
然後在瀏覽器中執行index.html可以看到,頁面中內容為“Second Step!”,表示我們的npm run build成功構建了我們的vue程式碼啦✌️。
webpack開發環境(npm run dev)配置
但是,這樣的開發方式還不是很方便,每次寫完程式碼之後,需要手動npm run build構建打包,再手動重新整理頁面。所以我們需要配置開發環境的執行指令碼。
首先,在package.json中新增dev命令指令碼:
"scripts": {
"build": "cross-env NODE_ENV=production --mode=production webpack --config webpack.config.js",
"dev": "cross-env NODE_ENV=development webpack-dev-server --mode=development --config webpack.config.js --open",
"test": "echo \"Error: no test specified\" && exit 1"
}
複製程式碼
關於dev指令碼命令解釋:
- 與build不同的是,dev指令碼使用了webpack-dev-server,webpack-dev-server是一個小型的Node.js Express伺服器,它使用webpack-dev-middleware來服務於webpack的包,用於開發者在開發中配置使用。
- --mode=development設定模式為開發環境
- --open設定後,會在伺服器啟動之後立即開啟頁面
關於webpack-dev-server的理解與使用,可以參考這篇文章?詳解webpack-dev-server的使用。
webpack-dev-server是獨立的包,所以進行安裝:
npm i -D webpack-dev-server
複製程式碼
對App.vue進行修改,將Second Step!修改為Third Step!
然後命令列中執行npm run dev,伺服器啟動成功,命令列中出現:Project is running at http://localhost:8080/,從瀏覽器中進入http://localhost:8080/訪問頁面,但是頁面中的內容依舊是Second Step!。
伺服器執行成功,但是頁面內容未更新,這是什麼原因呢?
檢查根目錄下的index.html看到我們引入js的路徑為/dist/bundle.js,但是進行npm run dev命令並沒有更新dist的bundle.js。
這是因為webpack-dev-server打包的內容是放在記憶體中的,並不會在真實的目錄中生成。
這裡只需要把index.html中的引入bundle.js的script標籤刪除即可。因為在前面加了html-webpack-plugin包,在執行過程中會自動對記憶體中的index.html插入js。所以不需要再手動插入。
注意:
如果還未使用html-webpack-plugin,則需要用publicPath來解決。設定devServer的publicPath為/dist/即可。
關於publicPath的理解可以參考這裡?Webpack中publicPath詳解
引入webpack-dev-server的目的就是為了在開發階段根據修改快速更新頁面,先試一下效果。修改App.vue內容為Third Step Updated!,然後Ctrl + s儲存看看頁面是否更新,在控制檯中可以看到這樣的提示:
[WDS] App updated. Recompiling...
bundle.js:7 [WDS] App hot update...
複製程式碼
可以看到進行了重新編譯和更新,頁面內容也進行了重新整理,不需要重新執行npm run dev。
##進階配置
自動清理dist資料夾
前面新增了hash的設定,每次npm run build的時候都會生成新的hash字尾的檔案,不會覆蓋之前的bundle.[hash].js,導致dist資料夾的內容越來越多。
這裡就可以使用clean-webpack-plugin包實現每次構建的時候自動清理dist資料夾,首先安裝clean-webpack-plugin包:
npm i -D clean-webpack-plugin
複製程式碼
webpack.config.js中引入clean-webpack-plugin包並在plugins中配置:
const CleanWebpackPlugin = require('clean-webpack-plugin');
if (process.env.NODE_ENV === 'production') {
config.plugins.push(new CleanWebpackPlugin(['dist']));
}
複製程式碼
配置之後,每次執行npm run build就可以清理dist資料夾之前的內容了。
新增css-loader
一開始就安裝了css-loader沒使用,就算在App.vue中新增了樣式也不會出錯,那麼css-loader到底是幹什麼的呢?
webpack的官方解釋:
css-loader 解釋(interpret) @import 和 url() ,會 import/require() 後再解析(resolve)它們。
在某些情況下,可能需要在js中引用css檔案,例如新增一些全域性的樣式配置或者是通過引入css檔案達到css修改熱更新的目的等。這時候需要在js中通過require('xxx.css')引入,但是執行專案會出現以下錯誤。
ERROR in ./src/text.css 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
複製程式碼
這時候在 webpack.config.js 中新增 css-loader 就能解決這個問題:
{
test: /\.css$/,
loader:'css-loader'
}
複製程式碼
所以 css-loader 是處理 css 檔案,將 css 裝載到 javascript。
注意
安裝最新版本的css-loader(2.1.0)在構建(npm run build)的時候會出現如下錯誤:
ValidationError: CSS Loader Invalid Options
options should NOT have additional properties
由於目前暫未找到解決方法,所以暫時安裝指定的舊版本(1.0.1),等找到解決方法之後會更新。
關於css-loader理解參考這裡?你真的知道 css-loader 怎麼用嗎?
關於style-loader理解參考這裡?style-loader詳細使用說明
關於樣式相關的loader對比可以參考這裡?style-loader、css-loader、mini-css-extract-plugin 區別
新增圖片處理loader
<template>
<div>
Third Step!
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
div {
width: 200px;
height: 200px;
background: url("./logo.png");
}
</style>
複製程式碼
其中的關鍵是background的url設定,執行npm run dev會發現報錯❗️:
ERROR in ./src/logo.png 1:0
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type.
複製程式碼
這個問題是專案不能識別圖片字尾的原因,所以新增引用資源的loader:
npm i -D url-loader
複製程式碼
webpack.config.js配置圖片相關的loader:
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader'
}
複製程式碼
然後專案就可以成功執行且引入圖片了✅。
引入less
安裝less和less-loader:
npm i -D less less-loader
複製程式碼
webpack.config.js中新增less的loader配置:
{
test: /\.less$/,
loader: 'style!css!less'
}
複製程式碼
然後既可以使用less了✌️!
提取css
extract-text-webpack-plugin只支援 webpack 4 以下提取 CSS 檔案,webpack 4使用extract-text-webpack-plugin包的alpha版本,安裝:
npm i -D extract-text-webpack-plugin@next
複製程式碼
在webpack.config.js中首先對vue檔案中的樣式做etract處理, 新增extractCSS的配置:
{
test: /\.vue$/,
loader:'vue-loader',
options: {
extractCSS: true
}
}
複製程式碼
然後在plugins中使用extract-text-webpack-plugin:
const ExtractTextPlugin = require('extract-text-webpack-plugin');
plugins: [
htmlPlugin,
new CleanWebpackPlugin(['dist']),
new ExtractTextPlugin('style.css')
]
複製程式碼
執行npm run build就可以成功單獨提取css了。
關於npm安裝@next的解釋參考這裡?npm使用小技巧
自動解析確定的擴充套件
當前配置中,引用.vue等字尾的檔案,不能省略字尾,必須明確寫出,否則,無法識別。
可以使用resolve的extensions配置,在webpack.config.js中新增下面的配置即可。
// other configurations
resolve: {
extensions: ['*', '.js', '.vue', '.json']
}
// other configurations
複製程式碼
現在就可以愉快的引入檔案且不新增字尾啦。
分離生產環境和開發環境的webpack配置,使用webpack-merge合併通用配置
這一部分官網具體有說:webpack.docschina.org/guides/prod…
在上面的配置中,使用 npm run dev 執行開發環境,npm run build 執行生產環境。開發環境和生產環境的配置都在 webpack.config.js 中,對於兩種環境不同的配置,使用 if 邏輯進行了判斷與單獨配置。當配置邏輯逐漸增加,if 中的邏輯會逐漸臃腫,所以有必要對生產環境和開發環境的配置進行分離。
建立webpack.config.dev.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const htmlPlugin = new HtmlWebpackPlugin({
// 生成的html的title
title: 'Vue Starter',
// 生成的html的檔名
filename: 'index.html',
// 注入bundle到body中
inject: 'body'
});
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, './dist/')
},
module: {
rules: [
{
test: /\.vue$/,
loader:'vue-loader',
options: {
extractCSS: true
}
},
{
test: /\.css$/,
loader:'css-loader'
},
{
test: /\.less$/,
loader: 'style!css!less'
},
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader'
}
]
},
plugins: [
htmlPlugin,
new ExtractTextPlugin('style.[hash].css')
],
resolve: {
extensions: ['*', '.js', '.vue', '.json']
},
devtool: false,
devServer: {
noInfo: true
}
};
module.exports = config;
複製程式碼
去掉了生產環境判斷新增配置的邏輯。
建立webpack.config.pro.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const htmlPlugin = new HtmlWebpackPlugin({
// 生成的html的title
title: 'Vue Starter',
// 生成的html的檔名
filename: 'index.html',
// 注入bundle到body中
inject: 'body'
});
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, './dist/')
},
module: {
rules: [
{
test: /\.vue$/,
loader:'vue-loader',
options: {
extractCSS: true
}
},
{
test: /\.css$/,
loader:'css-loader'
},
{
test: /\.less$/,
loader: 'style!css!less'
},
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader'
}
]
},
plugins: [
htmlPlugin,
new ExtractTextPlugin('style.[hash].css'),
new CleanWebpackPlugin(['dist'])
],
resolve: {
extensions: ['*', '.js', '.vue', '.json']
},
devtool: '#source-map',
devServer: {
noInfo: true
}
};
module.exports = config;
複製程式碼
將 if 邏輯刪除,直接配置生產環境需要的 plugins。
修改 package.json 中開發環境和生產環境的配置檔案:
"dev": "cross-env NODE_ENV=development webpack-dev-server --mode=development --config webpack.config.dev.js --open",
"build": "cross-env NODE_ENV=production webpack --mode=production --config webpack.config.pro.js",
複製程式碼
另外,除了一些特殊配置,可以看到還有很多相同的重複配置,本著 DRY 原則,可以提取通用的配置,然後使用 webpack-merge 進行合併。
首先安裝 webpack-merge:
npm i -D webpack-merge
複製程式碼
然後,將之前的 webpack.config.js 改名為 webpack.common.js,修改程式碼為 生產環境和開發環境通用的配置,主要是通用的 enter、output、module 和通用的 plugins。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const htmlPlugin = new HtmlWebpackPlugin({
// 生成的html的title
title: 'Vue Starter',
// 生成的html的檔名
filename: 'index.html',
// 注入bundle到body中
inject: 'body'
});
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, './dist/')
},
module: {
rules: [
{
test: /\.vue$/,
loader:'vue-loader',
options: {
extractCSS: true
}
},
{
test: /\.css$/,
loader:'css-loader'
},
{
test: /\.less$/,
loader: 'style!css!less'
},
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader'
}
]
},
plugins: [
htmlPlugin,
new ExtractTextPlugin('style.[hash].css')
],
resolve: {
extensions: ['*', '.js', '.vue', '.json']
},
};
module.exports = config;
複製程式碼
現在可以在 生產環境和開發環境的配置檔案中使用 webpack-merge 和 通用的 common 配置。
webpack.config.dev.js:
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: '#eval-source-map',
devServer: {
noInfo: true,
open: true
}
});
複製程式碼
webpack.config.pro.js:
const merge = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
plugins: [
new CleanWebpackPlugin(['dist'])
],
devtool: '#source-map',
});
複製程式碼
然後修改 package.json,目的在於將之前的 mode 設定直接放到配置檔案中,這樣可以集中處理生產環境和開發環境的區別配置。
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.dev.js",
"build": "cross-env NODE_ENV=production webpack --config webpack.config.pro.js",
複製程式碼
這樣就完成了,生產環境與開發環境的配置分離啦~✌️
關於 source map
首先什麼是 source map?
一句話來說,source map 就是一個資訊檔案,用來記錄打包檔案轉換前的位置資訊,便於除錯。
詳情參考阮一峰大大的博文:JavaScript Source Map 詳解
關於 webpack 中如何配置 source-map?
新增eslint
使用eslint可以保證保證編碼規範,自動檢驗程式碼,安裝:
npm i -D eslint eslint-config-standard babel-eslint eslint-config-vue eslint-plugin-vue eslint-plugin-standard eslint-plugin-promise eslint-plugin-import
複製程式碼
然後使用命令初始化eslint,會生成.eslintrc.js:
eslint --init
複製程式碼
在.eslintrc.js 寫入如下配置:
module.exports = {
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
},
extends: ['vue', 'standard', 'plugin:vue/recommended'],
plugins: [
'vue',
"standard",
"promise"
],
'rules': {
"indent": ["error", 4, { "SwitchCase": 1 }],
'arrow-parens': 0,
'generator-star-spacing': 0,
"semi": ["error", "always", { "omitLastInOneLineBlock": true }],
"no-lone-blocks": "error",
"no-multi-spaces": "error",
"no-multiple-empty-lines": ["error", { "max": 2 }],
"no-param-reassign": "warn",
"no-spaced-func": "error",
"no-use-before-define": "error",
"no-unused-vars": "error",
"no-with": "error",
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],
"comma-spacing": ["error", { "before": false, "after": true }],
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}
複製程式碼
然後在 package.json 中新增 lint 指令碼命令:
"lint": "eslint --ext .js,.vue src"
複製程式碼
就可以通過npm run lint進行檢查。
為了能在編寫程式碼的過程中編輯器就提醒錯誤,需要在webstorm -> Preferences -> Languages & Frameworks -> Javascript -> Code Quality Tools -> ESLint中勾選enable即可。