Webpack4.x 入門實踐之旅

查看完整配置參考能夠訪問:github.com/logcas/Webp…javascript

寫在前面的廢話

幾天前面試某公司前端實習生時被問各類原理,包括Vue做用域插槽、computed、props、data、watch實現原理和區別、Webpack原理、Webpack和Gulp原理上的不一樣.....只能一臉懵逼,感受本身真的好菜。css

Webpack以前沒有試過手動配置,因此就從Webpack開始了。大體看了一篇英文文檔,就開擼了。html

菜雞入門,若是不妥,但願個位大佬能提個醒,謝謝。前端

開始

首先使用npm init生成一個配置文件package.jsonjava

而後經過如下命令安裝Webpack依賴包:node

npm install webpack webpack-cli webpack-dev-server -D
複製代碼

如今,咱們就要編寫Webpack的配置文件。webpack

通常來講,咱們開發一個項目時,都分爲開發環境生產環境。對於開發環境,咱們須要搭建一個本地開發服務器,而且能夠追蹤代碼錯誤的位置等等;而對於生產環境,就要把代碼壓縮,不跟蹤代碼的錯誤位置等。因此,通常都把兩個環境的配置文件分開。可是,對於一些loader的配置其實是同樣的,咱們能夠複用公共的配置選項。git

所以,咱們把配置文件分爲三個:github

webpack.base.conf.js // 公共的基礎配置
webpack.prod.conf.js // 生產環境的配置
webpack.dev.conf.js // 開發環境的配置
複製代碼

爲了更方便管理,咱們把它們都放在build目錄下。web

首先咱們來編寫webpack.base.conf.js這部分,先把一個應有的配置整體框架寫出來:

module.exports = {
  // entry:
  // output:
  // module:
  // plugins: 
}
複製代碼

爲了更好地說明狀況,整個目錄結構咱們是這樣的:

/build // 存放Webpack的配置文件
/src // 存放開發時的源文件
/dist // 存放構建後的目標文件
/public // 存放一些靜態資源文件(如index.html)
/config // 存放一些通用配置文件
複製代碼

目錄

而後咱們對屬性逐個編寫。

entry 入口

entry就是要對Webpack說明入口文件,而後Webpack會根據入口文件和其中的依賴構建一個依賴圖。能夠傳入一個stringobject

傳入一個string是這樣的,能夠傳入一個絕對地址或相對地址:

module.exports = {
  entry: path.resolve(__dirname, '..', 'src', 'index.js',
};
複製代碼
module.exports = {
  entry: '../src/index.js',
};
複製代碼

若是傳入的是一個object,能夠說明多個入口,鍵名爲該入口文件的名字(後續的佔位符[name]):

module.exports = {
  entry: {
    'main': '../src/index.js',
    'app': '../src/app.js',
  },
};
複製代碼

這裏呢,咱們先在src目錄下建立一個main.js文件,而後寫上一句簡單的console.log('hello,world')。而後,咱們把entry填上,這裏我就比較喜歡用path.resolve把相對地址轉爲絕對地址,目前咱們的webpack.base.conf.js是這樣的:

module.exports = {
  entry: path.resolve(__dirname, '..', 'src', 'main.js',
}
複製代碼

搞定入口之後,固然要搞打包構建後文件在哪裏出來的問題了。

output 出口

output說明了經過Webpack打包構建後文件出來的位置、文件名等,主要有這樣一些屬性:

module.exports = {
  entry: path.resolve(__dirname, '..', 'src', 'main.js',
  output: {
    path: // 出口文件的目錄
    filename: // 文件的名稱
    chunkFilename: // 公共塊的名稱
    publicPath: // 文件輸出的公共路徑
    pathinfo: // 是否保留依賴包中的註釋,但基本不用管,當生產環境下默認不保留,開發環境保留。
  }
}
複製代碼

咱們進一步把webpack.base.conf.js填充:

const path = require('path');
module.exports = {
  entry: path.resolve(__dirname, '..', 'src', 'main.js'),
  output: {
    path: path.resolve(__dirname, '..', 'dist'),
    filename: '[name].[hash].js',
    chunkFilename: '[id].[hash].js',
  },
};
複製代碼

對於佔位符[name],它所代替的就是entry中的鍵名(若是entry是對象的話),由於咱們用了string做爲入口,那麼它的名字默認爲main,至於[hash]就是哈希值了,這方面主要用於解決緩存的問題,每次構建哈希值都不一樣,當咱們更新模塊後,由於哈希值不一樣了,所以會請求最新的JS文件。

實際上這時候,說明了入口和出口,咱們已經能夠對它進行構建了,這時咱們在終端敲下這樣的一個命令:

webpack --config build/webpack.base.conf.js
複製代碼

而後就構建成功了!

構建

可是,你會發現最下方有一行黃色的警告,它說你沒有指定modemode是爲了說明構建的模式是production(生產)仍是development(開發),雖然目前沒有指定,可是依然成功了,由於mode的默認值爲production,該模式下默認壓縮代碼,不信你能夠打開dist下的文件看看是否是成了一坨。咱們如今並無論mode,由於咱們只是編寫公共的配置,還要處理的事情不少呢。

module 模塊

Webpack雖然是一個很牛X的打包器,可是它只能識別JavaScriptJSON文件。對於其餘如TypeScriptimagefontcss等文件,咱們須要把它轉換爲Webpack能夠識別的代碼文件,這時候就須要用到loader,而loader是在module屬性中定義的。

經過Babel的幫助下把ES6用起來

ES6中有許多我很是喜歡的新東西,例如解構賦值、letconst、箭頭函數等等。可是並非全部瀏覽器都支持原生ES6的語法,咱們就要經過一些手段把它們轉化爲低版本的ECMAScript,這時Babel就登場了。一句話歸納,Babel是一個能夠把新的ECMAScript轉換成低版本的ECMAScript的東西,至於具體介紹請看它的官網了。

咱們要用它把咱們的ES6語法進行轉換,這時候就須要用到babel-loader

這裏須要注意的是,babel-loader 8.x版本必須與babel 7.x配合使用,babel-loader 7.x必須與babel 6.x配合使用,不然會出錯。別問爲何,問就跳樓,官方說的。

我就經過如下命令安裝了:

npm install -D babel-loader @babel/core @babel/preset-env
複製代碼

而後根據官方文檔咱們能夠這樣寫loader

module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      }
    ]
  }
複製代碼

test表示匹配的文件的正則表達式,exclude表示剔除這些文件不轉碼(node_modules下的文件確定不用轉了)。

而後在根目錄下(也就是package.json所在的目錄)新建一個配置文件.babelrc,寫入以下信息:

{
  "presets": ["@babel/preset-env"]
}
複製代碼

如今,咱們的webpack.base.conf.js就成這樣的了:

const path = require('path');
module.exports = {
  entry: path.resolve(__dirname, '..', 'src', 'main.js'),
  output: {
    path: path.resolve(__dirname, '..', 'dist'),
    filename: '[name].[hash].js',
    chunkFilename: '[id].[hash].js',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      }
    ]
  }
};
複製代碼

這時,咱們把main.js改爲如下的內容,而且把mode改成development,試試Babel的效果:

const hello = () => {
  console.log('ES6箭頭函數');
}

hello();
複製代碼

而後又是那條構建語句:

webpack --config build/webpack.base.conf.js
複製代碼

構建完成後,咱們發現代碼沒有壓縮了,而後咱們找到hello函數:

babel

你會發現箭頭函數轉化成了普通函數,說明Babel奏效了!

html

HTML的插曲

咱們目前雖然構建成功了,可是仍是看不到效果,由於咱們尚未結合HTML文件去加載JS文件。

這時候,爲了更好地介紹後面的各類loader,咱們就要先插入一段HTML的廣告了。

若是咱們想要在index.html裏引入dist目錄下打包好的JS文件,咱們會發現這樣一個問題:當咱們每打包一次,哈希值就變一次,而且文件名又長又臭,難道每次構建完都要手動修改嗎?答案是否認的,若是什麼都要手動,還要Webpack幹嗎。

這時候咱們就要用到plugins屬性了,也就是插件。咱們要安裝一個名叫html-webpack-plugin的包,而後new一個實例到plugins數組中。

首先是安裝它:

npm install html-webpack-plugin -D
複製代碼

而後就在webpack.base.conf.js中引入它,而後new一個實例,就像這樣:

const HtmlWebpackPlugin = require('html-webpack-plugin');
// ... 省略了其餘配置
plugins: [
  new HtmlWebpackPlugin({
    filename: 'index.html',
    template: path.resolve(__dirname, '../public', 'index.html'),
  }),
]
複製代碼

而後咱們在public目錄下寫下咱們的模板文件以後,又是那個構建命令:

webpack --config build/webpack.base.conf.js
複製代碼

你就會發如今構建後會在dist目錄下自動生成一個index.html了,而且它是以咱們編寫的模板爲基準,自動插入咱們打包好的腳本文件,至關方便啊有木有!

好吧,讓咱們回到loader的問題。

搞定樣式

咱們處理完了HTMLJavaScript的問題後,如今來處理CSS的問題。很顯然,Webpack依然是不能直接識別CSS文件的,所以咱們仍是須要藉助一些loader

  1. style-loader,把CSS內容構成一個<style></style>標籤加入到HTML中。
  2. css-loader,解析CSS文件的內容。

安裝它們:

npm install style-loader css-loader -D
複製代碼

而後修改webpack.base.conf.js文件中的module屬性,其餘內容不變:

module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          { loader: 'css-loader' },
        ]
      },
    ]
  },
複製代碼

咱們在src目錄下編寫一個main.css文件,而後在main.js中引入:

/* main.css */
h1 {
  color: blue;
}
複製代碼
// main.js
import './main.css';
const hello = () => {
  console.log('ES6箭頭函數');
}

hello();
複製代碼

而後構建,打開index.html文件你會發現h1標籤的字體已經變成了藍色,說明已經生效了!!!

css

使用SCSS等預處理語言

咱們平常更喜歡用SCSS這些去編寫樣式,由於它們更方便更靈活。所以,咱們又須要用loader去解析它們。

對於scss/sass,咱們須要用到如下兩個依賴:

  1. node-sass
  2. sass-loader

一樣咱們也是先安裝:

npm install node-sass sass-loader -D
複製代碼

不須要添加新的rules,只須要在原來解析CSS那裏添加上sass-loader就好了:

// 省略了不少不少...
      {
        test: /\.(sc|sa|c)ss$/,
        use: [
          { loader: 'style-loader' },
          { loader: 'css-loader' },
          { loader: 'sass-loader' },
        ]
      },
複製代碼

上面代表對於.scss\.css\.sass文件,loader的處理順序是sass-loader => css-loader => style-loader。由於,Webpackloader處理文件的順序是從右往左的。

而後咱們把樣式main.css更改成main.scss,而且在main.js中從新引入:

/* main.scss */
$myColor: blue;
h1 {
  color: $myColor;
}
複製代碼
// main.js
import './main.scss';
const hello = () => {
  console.log('ES6箭頭函數');
}

hello();
複製代碼

而後構建,打開index.html,效果依然存在,生效,完畢!

使用postcss添加瀏覽器樣式前綴

PostCSS 是一個容許使用 JS 插件轉換樣式的工具。 這些插件能夠檢查(lint)你的 CSS,支持 CSS Variables 和 Mixins, 編譯還沒有被瀏覽器普遍支持的先進的 CSS 語法,內聯圖片,以及其它不少優秀的功能。

咱們要使用PostCSS來爲一些屬性自動添加瀏覽器的前綴,就要安裝以下幾個包:

cnpm install postcss-loader autoprefixer -D
複製代碼

而後在原來的Webpack基礎上修改:

// 一樣省略了不少不少。。。
      {
        test: /\.(sc|sa|c)ss$/,
        use: [
          { loader: 'style-loader' },
          { loader: 'css-loader' },
          { loader: 'postcss-loader' },
          { loader: 'sass-loader' },
        ]
      },
複製代碼

而且在根目錄下建立一個名爲postcss.config.js的配置文件:

module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}
複製代碼

而後,就沒有了,由於已經完成了,很簡單吧?固然,PostCSS的做用確定不止自動添加前綴,你能夠看着文檔試試別的。

分離樣式

目前爲止,咱們的樣式是寫在JS文件上的。一般爲了更好地利用緩存機制,咱們須要把.css文件和.js文件分離。分離樣式須要用到一個叫mini-css-extract-plugin的插件。

先來下載它:

cnpm install mini-css-extract-plugin -D
複製代碼

而後咱們在plugins屬性中添加它的一個實例,而且把style-loader替換成MiniCssExtractPlugin.loader

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  // 省略了其餘配置...
  module: {
    rules: [
      {
        test: /\.(sc|sa|c)ss$/,
        use: [{
            loader: MiniCssExtractPlugin.loader,
          },
          {
            loader: 'css-loader'
          },
          {
            loader: 'postcss-loader'
          },
          {
            loader: 'sass-loader'
          },
        ]
      },
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[hash].css',
    }),
  ]
};
複製代碼

而後再次構建,咱們就會發現樣式文件已經分離到了dist/css中。

整理咱們的dist目錄

如今咱們看一下dist目錄就會發現,生成了N多個main.[hash].js文件。在咱們每次構建完後,都會生成一個哈希值不一樣的打包後的JS文件,這樣就會使咱們dist目錄下新舊文件都存在形成混亂而沒法管理的問題,畢竟對於dist目錄,咱們是但願把每次新打包的文件都放進去,而舊的就刪掉。咱們能夠經過一個叫clean-webpack-plugin的插件去解決這個問題。

首先安裝依賴:

npm install clean-webpack-plugin -D
複製代碼

而後在配置文件webpack.base.conf.js中引入:

const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
  // 省略了其餘部分...
  plugins: [
    new CleanWebpackPlugin(),
  ]
};
複製代碼

實際上咱們能夠傳入一個自定義的配置,例如new CleanWebpackPlugin(options)這樣子,options是咱們本身定義的一個配置對象,但就目前來說咱們使用默認配置就行了。有興趣的同窗能夠去npm官網搜索這個插件去閱讀它的選項內容。

而後咱們能夠進行一次構建,就會發現dist文件在構建前會被清理掉,而後構建完成後新的文件會從新寫入。

處理圖片、字體、音頻文件等資源

目前爲止咱們已經處理了JavaScriptHTMLCSS的問題,可是對於一個頁面來講,幾乎確定是包含圖片、字體、音頻這些文件的。由於Webpack只能處理JS和JSON文件的緣由,咱們依然須要對應的loader去處理這些資源文件,而且爲它們正確地引入到頁面中。

file-loader

file-loader能夠幫咱們完成這些事情,它能夠幫助咱們把開發時引入的資源文件放到正確的位置(也就是幫咱們把引用的路徑都弄好),首先咱們要先安裝它的包:

cnpm install file-loader -D
複製代碼

而後在配置文件webpack.base.conf.js寫入以下配置:

module.exports = {
  module: {
    rules: [
      // 圖片處理
      {
        test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
        use: [{
          loader: 'file-loader',
          options: {
            name: 'imgs/[name].[hash:8].[ext]'
          }
        }]
      },
      // SVG處理
      {
        test: /\.(svg)(\?.*)?$/,
        use: [{
          loader: 'file-loader',
          options: {
            name: 'img/[name].[hash:8].[ext]'
          }
        }]
      },
      // 音頻視頻文件處理
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        use: [{
          loader: 'file-loader',
          options: {
            name: 'media/[name].[hash:8].[ext]'
          }
        }]
      },
      // 字體文件處理
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
        use: [{
          loader: 'file-loader',
          options: {
            name: 'fonts/[name].[hash:8].[ext]'
          }
        }]
      },
    ]
  }
}
複製代碼

而後咱們在main.js中引入一個圖片:

// main.js
import './main.scss';
import myImg from './images/2.jpg';

window.onload = function() {
  let img = new Image();
  img.src = myImg;
  document.body.appendChild(img);
}
複製代碼

而且在main.scss中引入一個背景圖:

body {
  background-image: url(images/1.jpg);
  background-size: 100%;
  background-repeat: no-repeat;
}
複製代碼

而後打包構建,打開dist目錄下的index.html文件,你會發現圖片正常引入了。

資源

url-loader

咱們使用file-loader進行對圖片、字體等資源的處理,對於每一個圖片資源咱們都有一個地址,也就是說,當咱們把頁面放上去時,每一個圖片無論它的大小咱們都會發一個請求。但實際上,對於體積很小很小的圖片,發請求的效率很是低的。所以,咱們要把小體積的資源轉換成base64編碼,直接嵌套在文件中,而減小一次網絡請求,這時候就要用到url-loader

實際上,url-loaderfile-loader的一個上層封裝,當某個資源的體積小於必定的大小時,咱們經過url-loader處理,把這個資源轉換成base64編碼;當體育大於某個預設值時,咱們就把它交給file-loader處理,讓它正確處理資源引入的路徑。

咱們須要先安裝url-loader

npm install url-loader -D
複製代碼

而後修改webpack.base.conf.js中對於資源處理loader的配置:

rules: [
      // 圖片處理
      {
        test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
        use: [{
          loader: 'url-loader',
          options: {
            limit: 4096, // 當體積小於4096字節時,轉爲base64編碼
            fallback: {
              loader: 'file-loader', // 不然的話交給file-loader 去處理
              options: {
                name: 'img/[name].[hash:8].[ext]'
              }
            }
          }
        }]
      },
      // SVG處理
      {
        test: /\.(svg)(\?.*)?$/,
        use: [{
          loader: 'file-loader',
          options: {
            name: 'imgs/[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]'
              }
            }
          }
        }]
      },
    ]
複製代碼

使用npm script

直到如今,咱們每次構建項目的時候,都要輸入一長串的命令,而且要指定構建文件,很是麻煩。爲了更方便去執行構建的命令,咱們可使用npm script的功能。

咱們打開package.json,而後在scripts屬性上加入下面這幾行:

"scripts": {
  "build": "webpack --config build/webpack.prod.conf.js",
  "dev": "webpack --config build/webpack.dev.conf.js",
},
複製代碼

保存以後,咱們之後的每次構建,當咱們須要打包成生產環境使用的時候,只須要敲下npm run build命令,它會爲咱們執行後面那一長串的命令;當須要打包成開發環境使用的時候,也就只須要敲下npm run dev就能夠了。

固然,如今是不行的。由於咱們尚未編寫webpack.prod.conf.jswebpack.dev.conf.js

OK,咱們如今就來編寫它們!!

使用webpack-merge

咱們基本上已經編寫完了公共配置的部分,實際上只須要合併到對應環境的配置文件就能夠了。可是咱們確定不是手動複製粘貼過去。這時候就要用到一個叫webpack-merge的包,它能夠幫咱們把公共部分和對應環境特定的配置合併起來。

先安裝:

npm install webpack-merge -D
複製代碼

而後咱們分別編寫webpack.prod.conf.jswebpack.dev.conf.js

// webpack.dev.prod.js
const baseConfig = require('./webpack.base.conf');
const merge = require('webpack-merge');

module.exports = merge(baseConfig, {
  mode: 'production',
});
複製代碼
// webpack.dev.conf.js
const baseConfig = require('./webpack.base.conf');
const merge = require('webpack-merge');

module.exports = merge(baseConfig, {
  mode: 'development',
});
複製代碼

目前兩個構建文件的差異只在mode不一樣,但後面當你真正配置更多東西的時候兩個環境的配置是有更大的差異的(例如壓縮、使用dev-tool、報錯顯示等等)。

而後咱們就可使用npm script去分別構建不一樣環境的文件了。

如今咱們把構建文件分爲了兩個,分別對應着不一樣環境的構建。這時候就要修正一下webpack.base.conf.js了。

由於以前咱們都是用webpack --config build/webpack.base.conf.js去進行構建的,因此咱們把clean-webpack-plugin寫在了裏面,以便每次構建前都清空dist目錄。實際上咱們並不須要在開發環境下這樣作,只須要在生產環境下便可。所以,咱們把它從webpack.base.conf.js中提取出來,剪切到webpack.prod.conf.js中:

// webpack.prod.conf.js
const baseConfig = require('./webpack.base.conf');
const merge = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = merge(baseConfig, {
  mode: 'production',
  plugins: [
    new CleanWebpackPlugin(),
  ]
});
複製代碼

搭建本地服務器和熱更新

目前咱們每次調試時都是手動打開index.html或者刷新。可是,爲了更裝逼,咱們要搭建一個本地服務器,而後經過http://localhost:xxxx去訪問它,這時候就要用到webpack-dev-server

webpack-dev-server在開篇的時候不知道你有沒有注意到,它已經安裝了。

因爲本地服務器僅在開發時使用,因此咱們只須要在webpack.dev.conf.js中寫就行了。

const path = require('path');

module.exports = merge(baseConfig, {
  mode: 'development',
  devServer: {
    port: 8081, // 端口
    contentBase: path.resolve(__dirname, '../dist'), // 靜態文件目錄
    hot: true, // 是否熱更新
    compress: true, // 是否開啓Gzip壓縮
    host: 'localhost', // 主機
  }
});
複製代碼

而後咱們修改package.json文件,讓開發環境構建使經過webpack-dev-server啓動:

"scripts": {
  "dev": "webpack-dev-server --config build/webpack.dev.conf.js --open",
},
複製代碼

而後本地服務器http://localhost:8081就出來了,而且顯示index.html文件的內容。當咱們修改腳本或者樣式時,也不須要刷新。

本地服務器

OK,基本的環境就搭建完畢了!(當前目前是沒有任何優化的)

相關文章
相關標籤/搜索