「基礎搭建」從零開始,基於 Webpack5 搭建一個 Vue-Cli

Sunshine_Lin發表於2022-03-16

前言

大家好,我是林三心,用最通俗易懂的話講最難的知識點是我的座右銘,基礎是進階的前提是我的初心

背景

大家平時在進行Vue開發的時候,大部分人都是使用 Vue-cli 這個現成的Vue腳手架來進行開發的,但是用它用了這麼久,你難道不想自己搭一個屬於自己的 Vue-cli 嗎?

今天我就帶大家來搭建一個基本的 Vue-cli ,也可以讓大家對 Webpack 有更深入的瞭解!建議大家一定要跟著我一步一步來哦!

事先說明:本文只介紹vue-cli基本配置,關於優化、規範這兩方面,我後面會再寫兩篇文章進行講解

1、建一個資料夾

新建一個資料夾my-vue-cli用來存放專案

2、初始化npm

在終端中輸入

npm init

然後一直回車就行,這樣能使專案擁有一個npm管理環境,之後可以在此環境上安裝我們所需要的包

3、webpack、webpack-cli

安裝 webpack、webpack-cli

  • webpack :打包的工具
  • webpack-cli :為webpack提供命令列的工具

    npm i webpack webpack-cli -D

4、src、public

在根目錄下新建 src、public 這兩個資料夾,前者用來放置專案主要程式碼,後者用來放專案公用靜態資源

  • public/index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>my-vue-cli</title>
    </head>
    <body>
    <div id="app"></div>
    </body>
    </html>
  • src/main.js

    import { add } from './tools/add.js'
    
    console.log(add(1, 2))
    console.log('我是main.js')
  • src/tools/add.js

    export const add = (a, b) => {
    return a + b
    }

5、入口檔案

剛剛的 main.js 就是我們的入口檔案,也就相當於整個引用樹的根節點,webpack打包需要從入口檔案開始查詢,一直到打包所有引用檔案。

進行入口檔案的配置,在根目錄下新建 webpack.config.js

const path = require('path')

module.exports = {
  // 模式 開發模式
  mode: 'development',
  // 入口檔案 main.js
  entry: {
    main: './src/main.js'
  },
  // 輸出
  output: {
    // 輸出到 dist資料夾
    path: path.resolve(__dirname, './dist'),
    // js檔案下
    filename: 'js/chunk-[contenthash].js',
    // 每次打包前自動清除舊的dist
    clean: true,
  }
}

6、配置打包命令

package.json 裡配置打包命令:

"scripts": {
    "build": "webpack"
},

現在我們到終端輸入 npm run build ,就能發現打包成功:

但是這其實不是我們要的目的,我們的目的是將這個打包後的最終js檔案,插入到剛剛的 index.html 中,因為js檔案得讓html檔案引用,才有意義嘛!所以我們不僅要打包js,還要打包html

小知識:loader和plugin

  • loader :使webpack擁有解析非js檔案的能力,如css、png、ts等等
  • plugin :擴充webpack的打包功能,如優化體積、顯示進度條等等

7、打包html

打包html需要用到 html-webpack-plugin 這個外掛,也就是plugin,所以需要安裝一下:

npm i html-webpack-plugin -D

並且需要在 webpack.config.js 中配置一下

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  // 剛剛的程式碼...
  
  // 外掛都放 plugins 中
  plugins: [
    new HtmlWebpackPlugin({
      // 選擇模板 public/index.html
      template: './public/index.html',
      // 打包後的名字
      filename: 'index.html',
      // js檔案插入 body裡
      inject: 'body',
    }),
  ]
}

現在我們可以在終端中執行打包命令 npm run build 可以看到html被打包了,且打包後的html自動引入打包後的js檔案

現在我們可以開啟打包後的 index.html ,發現控制檯可以輸出,說明成功了!

打包CSS

src 下新建 styles 資料夾,用來存放樣式檔案檔案

  • src/styles/index.scss

    body {
    background-color: blue;
    }

    然後我們在入口檔案 main.js 中引入

    import './styles/index.scss'
    
    // 剛剛的程式碼...

    我們的目的是,打包 index.scss 這個檔案,並且讓 index.html 自動引入打包後的css檔案,所以我們需要安裝以下幾個東西:

  • sass、sass-loader :可以將scss程式碼轉成css
  • css-loader :使webpack具有打包css的能力
  • sass-resources-loader :可選,支援打包全域性公共scss檔案
  • mini-css-extract-plugin :可將css程式碼打包成一個單獨的css檔案

我們安裝一下這些外掛

npm i 
sass
sass-loader
sass-resources-loader
mini-css-extract-plugin
-D

然後配置一下 webpack.config.js

// 剛才的程式碼...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
  // 剛才的程式碼...
  plugins: [
    // 剛才的程式碼...
    new MiniCssExtractPlugin({
      // 將css程式碼輸出到dist/styles資料夾下
      filename: 'styles/chunk-[contenthash].css',
      ignoreOrder: true,
    }),
  ],
  module: {
    rules: [
      {
        // 匹配檔案字尾的規則
        test: /\.(css|s[cs]ss)$/,
        use: [
          // loader執行順序是從右到左
          MiniCssExtractPlugin.loader,
          'css-loader',
          'sass-loader',
          // {
          //   loader: 'sass-resources-loader',
          //   options: {
          //     resources: [
          //       // 放置全域性引入的公共scss檔案
          //     ],
          //   },
          // },
        ],
      },
    ]
  }
}

此時我們重新執行打包命令 npm run build ,可以發現出現了打包後的css檔案,且 index.html 中自動引入了css檔案:

我們可以看看頁面,可以看到,body的背景已經變成藍色,說明有效果了:

打包圖片

webpack5中已經廢棄了 url-loader ,打包圖片可以使用 asset-module ,我們先放置一張圖片在 src/assets/images 中:

並且改寫一下 index.css

body {
  width: 100vw;
  height: 100vh;
  // 引入背景圖片
  background-image: url('../assets/images/guang.png');
  background-size: 100% 100%;
}

然後我們在 webpack.config.js 中新增打包圖片的配置

  module: {
    rules: [
      // 剛剛的程式碼...
      {
        // 匹配檔案字尾的規則
        test: /\.(png|jpe?g|gif|svg|webp)$/,
        type: 'asset',
        parser: {
          // 轉base64的條件
          dataUrlCondition: {
             maxSize: 25 * 1024, // 25kb
          }
        },
        generator: {
          // 打包到 dist/image 檔案下
         filename: 'images/[contenthash][ext][query]',
        },
     }
    ]
  }

我們現在重新執行一下 npm run build ,發現dist下已經有了 images 這個資料夾

我們看一下頁面背景圖片已經生效,說明打包成功了!

配置babel

babel 可以將我們專案中的高階語法轉化成比較低階的語法,比如可以將 ES6 轉為 ES5 ,這樣可以相容一些低版本瀏覽器,所以是很有必要的

首先安裝所需的包:

  • @babel/core、babel-loader :轉換語法的工具
  • @babel/preset-env :轉換的一套現成規則
  • @babel/plugin-transform-runtime :轉換async/await所需外掛

    npm i
    @babel/core babel-loader
    @babel/preset-env
    @babel/plugin-transform-runtime
    -D

    由於 babel 是針對js檔案的語法轉換,所以我們需要在 webpack.config.js 中去針對js進行操作

    module: {
      rules: [
        // 剛剛的程式碼...
        {
          // 匹配js字尾檔案
          test: /\.js$/,
          // 排除node_modules中的js
          exclude: /node_modules/,
          use: [
            'babel-loader'
          ],
        }
      ]
    }

    單單配置了 babel-loader 還是不夠的,我們還需要配置 babel 轉換的規則,所以需要在根目錄下建立 babel.config.js

    // babel.config.js
    
    module.exports = {
    presets: [
      // 配置規則
      "@babel/preset-env"
    ],
    // 配置外掛
    plugins: ["@babel/plugin-transform-runtime"]
    }

    此時我們重新執行打包 npm run build ,我們可以發現打包後的js程式碼中,已經把剛剛程式碼中的 ES6 語法轉成 ES5 語法了!可以看到剛剛程式碼中的 const 已經轉成 ES5 語法了

打包Vue

打包Vue需要用到以下幾個包:

  • vue :Vue開發所需的依賴
  • vue-loader :解析 .vue 檔案的loader
  • vue-template-compiler :解析vue中模板的工具
  • @vue/babel-preset-jsx :支援解析vue中的jsx語法
注意: vue vue-template-compiler 版本需要一致,這裡我使用 2.6.14 這個版本, vue-loader 這裡我使用了 15.9.8 這個版本

所以我們先安裝一下:

npm i 
vue@2.6.14 vue-template-compiler@2.6.14
vue-loader@15.9.8 @vue/babel-preset-jsx
-D

然後我們需要去 webpack.config.js 中配置對 .vue 檔案的解析

// 剛才的程式碼...
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  // 剛才的程式碼...
  plugins: [
    // 剛才的程式碼...
    new VueLoaderPlugin()
  ],
  module: {
    rules: [
      // 剛才的程式碼...
      {
        test: /\.vue$/,
        use: 'vue-loader',
      }
    ]
  }
}

並且到 babel.config.js 中配置一下,讓webpack支援 .vue 檔案中的 jsx 語法

module.exports = {
  presets: [
    "@babel/preset-env",
    // 支援vue中的jsx語法
    "@vue/babel-preset-jsx"
  ],
  plugins: ["@babel/plugin-transform-runtime"]
}

現在我們可以在 src 下新建一個 App.vue

<template>
  <div class="box">我是App哈哈哈哈</div>
</template>

<script>
export default {}
</script>

<style lang="scss">
.box {
  width: 500px;
  height: 200px;
  color: #fff;
  background-color: #000;
}
</style>

然後改寫一下 src/main.js

import Vue from 'vue'
import App from './App.vue'

new Vue({
  render: (h) => h(App),
}).$mount('#app')

此時我們重新執行 npm run build ,我們可以看看頁面的效果,說明打包成功啦!

配置路徑別名

有時候檔案引用擱著太多層,引用起來會看起來很不明確,比如
../../../../../App.vue ,所以我們可以配置一下別名 alia

module.exports = {
  // 剛才的程式碼...
  resolve: {
    // 路徑別名
    alias: {
      '@': path.resolve('./src'),
      assets: '~/assets',
      tools: '~/tools'
    },
    // 引入檔案時省略字尾
    extensions: ['.js', '.ts', '.less', '.vue'],
  },
}

現在別名配置完成啦:

  • 配置前: ../../../../../App.vue
  • 配置後: @/App.vue

webpack-dev-server

剛剛我們發現,每改一次程式碼就得重新打包一次,非常繁瑣,有沒有可以改程式碼自動重新打包的呢?這就要用到 webpack-dev-server

npm i webpack-dev-server -D

webpack.config.js 中配置 devServer

  devServer: {
    // 自定義埠號
    // port:7000,
    // 自動開啟瀏覽器
    open: true
  },

然後到 package.json 中配置一下啟動命令

  "scripts": {
    "build": "webpack",
    "serve": "webpack serve"
  },

此時我們執行 npm run serve 就可以啟動專案啦!

區分環境

我們不能把所有配置都配置在一個 webpack.config.js 中,因為我們有兩個環境 development(開發環境)、production(生產環境) ,所以我們在根目錄下建立 build資料夾 ,並建立三個檔案

  • webpack.base.js :兩個環境共用配置

    • 入口,輸出配置
    • 各種檔案的處理
    • 進度條展示
    • 路徑別名
  • webpack.dev.js :開發環境獨有配置

    • webpack-dev-server
    • 不同的source-map模式
    • 不同的環境變數
  • webpack.prod.js :生產環境獨有配置

    • 不同的source-map模式
    • 不同的環境變數

我們需要先安裝一個合併外掛 webpack-merge ,用於兩個環境的配置可以合併公共的配置

npm i webpack-merge -D

然後我們在根目錄下新建一個 build資料夾 ,並在此資料夾下新建 webpack.base.js、webpack.dev.js、webpack.config.js

  • webpack.base.js

    // 公共配置
    
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    const { VueLoaderPlugin } = require('vue-loader')
    module.exports = {
    // 入口檔案 main.js
    entry: {
      main: './src/main.js'
    },
    // 輸出
    output: {
      // 輸出到 dist資料夾
      // 記得改路徑
      path: path.resolve(__dirname, '../dist'),
      // js檔案下
      filename: 'js/chunk-[contenthash].js',
      // 每次打包前自動清除舊的dist
      clean: true,
    },
    plugins: [
      new HtmlWebpackPlugin({
        // 選擇模板 public/index.html
        template: './public/index.html',
        // 打包後的名字
        filename: 'index.html',
        // js檔案插入 body裡
        inject: 'body',
      }),
      new MiniCssExtractPlugin({
        // 將css程式碼輸出到dist/styles資料夾下
        filename: 'styles/chunk-[contenthash].css',
        ignoreOrder: true,
      }),
      new VueLoaderPlugin()
    ],
    module: {
      rules: [
        {
          // 匹配檔案字尾的規則
          test: /\.(css|s[cs]ss)$/,
          use: [
            // loader執行順序是從右到左
            MiniCssExtractPlugin.loader,
            'css-loader',
            'sass-loader',
            // {
            //   loader: 'sass-resources-loader',
            //   options: {
            //     resources: [
            //       // 放置全域性引入的公共scss檔案
            //     ],
            //   },
            // },
          ],
        },
        {
          // 匹配檔案字尾的規則
          test: /\.(png|jpe?g|gif|svg|webp)$/,
          type: 'asset',
          parser: {
            // 轉base64的條件
            dataUrlCondition: {
              maxSize: 25 * 1024, // 25kb
            }
          },
          generator: {
            // 打包到 dist/image 檔案下
            filename: 'images/[contenthash][ext][query]',
          },
        },
        {
          test: /\.js$/,
          // 排除node_modules中的js
          exclude: /node_modules/,
          use: [
            'babel-loader'
          ],
        },
        {
          test: /\.vue$/,
          use: 'vue-loader',
        }
      ]
    },
    resolve: {
      // 路徑別名
      alias: {
        '@': path.resolve('./src'),
        assets: '~/assets'
      },
      // 引入檔案時省略字尾
      extensions: ['.js', '.ts', '.less', '.vue']
    },
    }
  • webpack.dev.js

    // 開發環境
    
    const { merge } = require('webpack-merge')
    const base = require('./webpack.base')
    
    module.exports = merge(base, {
    mode: 'development',
    devServer: {
      open: true,
      // hot: true,
    }
    })
  • webpack.prod.js

    // 生產環境
    
    const { merge } = require('webpack-merge')
    const base = require('./webpack.base')
    
    module.exports = merge(base, {
    mode: 'production'
    })

然後我們到 package.json 修改一下指令

  "scripts": {
    "serve": "webpack serve --config ./build/webpack.dev",
    "build": "webpack --config ./build/webpack.prod"
  },

接下來我們執行這兩個命令,發現都成功了:

  • npm run build
  • npm run serve

構建進度條

無論是啟動專案時還是打包時,都需要進度條的展示,所以需要把進度條配置在 webpack.base 中,我們需要先安裝進度條的外掛 progress-bar-webpack-plugin

npm i progress-bar-webpack-plugin -D
// webpack.base.js

// 剛剛的程式碼...
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const chalk = require('chalk')

module.exports = {
  // 剛剛的程式碼...
  plugins: [
    // 剛剛的程式碼...
    new ProgressBarPlugin({
      format: ` build [:bar] ${chalk.green.bold(':percent')} (:elapsed seconds)`,
    })
  ],
  // 剛剛的程式碼...
}

現在我們可以看到無論啟動專案或者打包,都會有進度條了

source-map

source-map 的作用:程式碼報錯時,能快速定位到出錯位置, webpack5 的所有 source-map模式 可以看webpack官網:https://webpack.docschina.org...

這裡我使用兩種模式:

  • development :使用 eval-cheap-module-source-map 模式,能具體定位到原始碼位置和原始碼展示,適合開發模式,體積較小
  • production :使用 nosources-source-map ,只能定位原始碼位置,不能原始碼展示,體積較小,適合生產模式

所以我們開始配置 source-map

  • webpack.dev.js

    // 剛才的程式碼...
    module.exports = merge(base, {
    // 剛才的程式碼...
    devtool: 'eval-cheap-module-source-map'
    })
  • webpack.prod.js

    // 剛才的程式碼...
    module.exports = merge(base, {
    // 剛才的程式碼...
    devtool: 'nosources-source-map'
    })

環境變數

配置 devlopment、production 這兩個環境的環境變數

  • webpack.dev.js

    // 剛才的程式碼...
    const webpack = require('webpack')
    
    module.exports = merge(base, {
    // 剛才的程式碼...
    plugins: [
      // 定義全域性變數
      new webpack.DefinePlugin({
        process: {
          env: {
            NODE_DEV: JSON.stringify('development'),
            // 這裡可以定義你的環境變數
            // VUE_APP_URL: JSON.stringify('https://xxx.com')
          },
        },
      }),
    ]
    })
  • webpack.prod.js

    // 剛才的程式碼...
    const webpack = require('webpack')
    
    module.exports = merge(base, {
    // 剛才的程式碼...
    plugins: [
      // 定義全域性變數
      new webpack.DefinePlugin({
        process: {
          env: {
            NODE_DEV: JSON.stringify('prodction'),
            // 這裡可以定義你的環境變數
            // VUE_APP_URL: JSON.stringify('https://xxx.com')
          },
        },
      }),
    ]
    })

優化、規範

關於優化、規範,我會另外再寫兩篇文章來講

結語

我是林三心,一個熱心的前端菜鳥程式設計師。如果你上進,喜歡前端,想學習前端,那我們們可以交朋友,一起摸魚哈哈,摸魚群,加我請備註【思否】

image.png

相關文章