面試官:自己搭建過vue開發環境嗎?

lentoo發表於2019-04-29

開篇

前段時間,看到群裡一些小夥伴面試的時候被面試官問到這類題目。平時大家開發vue專案的時候,相信大部分人都是使用 vue-cli腳手架生成的專案架構,然後 npm run install 安裝依賴,npm run serve啟動專案然後就開始寫業務程式碼了。

但是對專案裡的webpack封裝和配置瞭解的不清楚,容易導致出問題不知如何解決,或者不會通過webpack去擴充套件新功能。

該篇文章主要是想告訴小夥伴們,如何一步一步的通過 webpack4來搭建自己的vue開發環境

首先我們要知道 vue-cli生成的專案,幫我們配置好了哪些功能?

  1. ES6程式碼轉換成ES5程式碼
  2. scss/sass/less/styluscss
  3. .vue檔案轉換成js檔案
  4. 使用 jpgpngfont等資原始檔
  5. 自動新增css各瀏覽器產商的字首
  6. 程式碼熱更新
  7. 資源預載入
  8. 每次構建程式碼清除之前生成的程式碼
  9. 定義環境變數
  10. 區分開發環境打包跟生產環境打包
  11. ....

面試官:自己搭建過vue開發環境嗎?

1. 搭建 webpack 基本環境

該篇文章並不會細講 webpack 是什麼東西,如果還不是很清楚的話,可以先去看看 webpack官網

簡單的說,webpack是一個模組打包機,可以分析你的專案依賴的模組以及一些瀏覽器不能直接執行的語言jsxvue等轉換成 jscss檔案等,供瀏覽器使用。

面試官:自己搭建過vue開發環境嗎?

1.1 初始化專案

在命令列中執行 npm init 然後一路回車就行了,主要是生成一些專案基本資訊。最後會生成一個 package.json 檔案

npm init
複製程式碼

1.2 安裝webpack

面試官:自己搭建過vue開發環境嗎?

1.3 寫點小程式碼測試一下webpack是否安裝成功了

新建一個src資料夾,然後再建一個main.js檔案

// src/main.js
console.log('hello webpack')
複製程式碼

然後在 package.json 下面加一個指令碼命令

面試官:自己搭建過vue開發環境嗎?

然後執行該命令

npm run serve
複製程式碼

如果在 dist 目錄下生成了一個mian.js檔案,則表示webpack工作正常

2. 開始配置功能

  • 新建一個 build 資料夾,用來存放 webpack配置相關的檔案
  • build資料夾下新建一個webpack.config.js,配置webpack的基本配置
  • 修改 webpack.config.js配置

面試官:自己搭建過vue開發環境嗎?

  • 修改package.json 檔案,將之前新增的 serve 修改為
"serve": "webpack ./src/main.js --config ./build/webpack.config.js"
複製程式碼

2.1 配置 ES6/7/8ES5程式碼

  • 安裝相關依賴
npm install babel-loader @babel/core @babel/preset-env
複製程式碼
  • 修改webpack.config.js配置

面試官:自己搭建過vue開發環境嗎?

  • 在專案根目錄新增一個 babel.config.js 檔案

面試官:自己搭建過vue開發環境嗎?

  • 然後執行 npm run serve 命令,可以看到 ES6程式碼被轉成了ES5程式碼了

2.1.1 ES6/7/8 Apies5

babel-loader只會將 ES6/7/8語法轉換為ES5語法,但是對新api並不會轉換。

我們可以通過 babel-polyfill 對一些不支援新語法的客戶端提供新語法的實現

  • 安裝
npm install @babel/polyfill
複製程式碼
  • 修改webpack.config.js配置

entry 中新增 @babel-polyfill

面試官:自己搭建過vue開發環境嗎?

2.2 配置 scsscss

在沒配置 css 相關的 loader 時,引入scsscss相關檔案打包的話,會報錯

  • 安裝相關依賴
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配置

面試官:自己搭建過vue開發環境嗎?

2.3 配置 postcss 實現自動新增css3字首

  • 安裝相關依賴
npm install postcss-loader autoprefixer -D
複製程式碼
  • 修改webpack.config.js配置

面試官:自己搭建過vue開發環境嗎?

  • 在專案根目錄下新建一個 postcss.config.js

面試官:自己搭建過vue開發環境嗎?

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 配置
    面試官:自己搭建過vue開發環境嗎?

2.4 配置 devServer 熱更新功能

通過程式碼的熱更新功能,我們可以實現不重新整理頁面的情況下,更新我們的頁面

  • 安裝依賴
npm install webpack-dev-server -D
複製程式碼
  • 修改webpack.config.js配置

通過配置 devServerHotModuleReplacementPlugin 外掛來實現熱更新

面試官:自己搭建過vue開發環境嗎?

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()
  ]
}
複製程式碼
  • 測試一下
  1. 在 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>
複製程式碼
  1. 修改 main.js
import Vue from 'vue'
import App from './App.vue'

new Vue({
  render: h => h(App)
}).$mount('#app')
複製程式碼
  1. 執行一下

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 開發環境

  1. 不需要壓縮程式碼
  2. 需要熱更新
  3. css不需要提取到css檔案
  4. sourceMap
  5. ...

5.2 生產環境

  1. 壓縮程式碼
  2. 不需要熱更新
  3. 提取css,壓縮css檔案
  4. sourceMap
  5. 構建前清除上一次構建的內容
  6. ...
  • 安裝所需依賴
npm i @intervolga/optimize-cssnano-plugin mini-css-extract-plugin clean-webpack-plugin webpack-merge copy-webpack-plugin -D
複製程式碼
  1. @intervolga/optimize-cssnano-plugin 用於壓縮css程式碼
  2. mini-css-extract-plugin 用於提取css到檔案中
  3. clean-webpack-plugin 用於刪除上次構建的檔案
  4. webpack-merge 合併 webpack配置
  5. 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 替我們做了什麼

面試官:自己搭建過vue開發環境嗎?

推薦閱讀

  1. 使用 webpack 的各種外掛提升你的開發效率

  2. vue-cli3 專案從搭建優化到docker部署

  3. Event Loop 原來是這麼回事

  4. 通過vue-cli3構建一個SSR應用程式

歡迎關注

歡迎關注公眾號“碼上開發”,每天分享最新技術資訊

image

相關文章