從零搭建一個vue專案

蔥白吾發表於2019-04-28

最近感覺自己越來越像一個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指令碼命令解釋:

  1. cross-env依賴用於跨平臺設定環境變數。在多數情況下,在windows平臺下使用類似於: NODE_ENV=production的命令列指令會卡住;windows平臺與POSIX在使用命令列時有許多區別(POSIX,使用$ENV_VAR;windows使用%ENV_VAR%)。cross-env就是解決這類跨平臺問題的,統一每個平臺的命令。
  2. NODE_ENV=development 設定 NODE 的環境變數為開發環境
  3. --mode=procution配置為生產環境模式
  4. --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指令碼命令解釋:

  1. 與build不同的是,dev指令碼使用了webpack-dev-server,webpack-dev-server是一個小型的Node.js Express伺服器,它使用webpack-dev-middleware來服務於webpack的包,用於開發者在開發中配置使用。
  2. --mode=development設定模式為開發環境
  3. --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?

參考:Webpack中的sourcemap以及如何在生產和開發環境中合理的設定sourcemap的型別

新增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即可。

相關文章