一行一行手敲webpack4配置

代碼:githubjavascript


1、webpack4--基本配置

1.初始化配置

mkdir webpack4
cd webpack4
mkdir demo1
cd demo1
npm init -y 或 npm init

目錄結構

webpack4
├── webpack4/demo1
│   └── webpack4/demo1/package.json
└── webpack4/README.md
  • 安裝webpack

npm install webpack --save-devcss

  • 安裝指定版本webapck

npm install --save-dev webpack@<version>html

  • webpack 4+ 版本,還須要安裝webpack-cli

npm install webpack-cli --save-devjava

  • npx webpack -v:查看webpack版本
  • npx webpack-cli -v:查看webpack-cli版本
推薦本地安裝webpack和webpack-cli
寫這篇博客的時候webpack最新版本爲:4.30.0,也是這篇學習webpack4使用的版本

在demo1目錄下新建src目錄,在src目錄下新建index.jsnode

mkdir src 
cd src
touch index.js

demo1目錄結構webpack

demo1
├── demo1/package.json
├── demo1/package-lock.json
└── demo1/src
    └── demo1/src/index.js

在index.js中加寫代碼,例如:git

//index.js

let demo='webpack4'
console.log(demo)

webpack4能夠零配置打包,webpack4會默認對src目錄下的index.js文件打包。
如今運行npx webapck,能夠在demo1目錄下看到dist目錄,dist目錄下有一個main.js文件,這就是打包後的文件,打開查找能夠看到 console.log(demo),說明index.js被打包到main.js中。github

2.webpack4的簡單配置

demo1目錄下新建webpack配置文件webpack.config.jsweb

配置webpack--webpack.config.js
const path = require('path')

module.exports={
  //mode development: 開發環境 production:生產環境
  mode: 'development', 
  //entry 入口文件配置  
  entry: {
    index: './src/index.js'
  },
  //打包完成後文件輸出位置配置
  output: {
    //filename 設置打包後文件的名字
    //若是不設置filename,則文件的名字跟入口文件路徑的屬性名同樣
    filename: 'bundle.js',
    //path 設置打包完成後文件輸出路徑
    path: path.resolve(__dirname,'dist')
  }
}

運行npx webpack命令
npx webpack等價於npx webpack --config webpack.config.jschrome

webapck配置文件命名爲webpack.config.js時能夠省略--config *.js,直接執行npx webpack便可,不然執行npx webpack --config 配置文件名

看到dist目錄下有bundle.js,說明webpack配置正確。

package.json中配置'script'

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

添加"build": "webpack",運行npm run build效果等價於執行npx webpack命令。

配置webpack.config.js的modoule對象

loader的用法
file-loader的使用
安裝 file-loader
npm i file-loader --save-dev

webpack.config.js

const path = require('path')

module.exports={
  //mode development: 開發環境 production:生產環境
  mode: 'development', 
  //entry 入口文件配置  
  entry: {
    index: './src/index.js'
  },
  //打包完成後文件輸出位置配置
  output: {
    //filename 設置打包後文件的名字
    //若是不設置filename,則文件的名字跟入口文件路徑的屬性名同樣
    filename: 'bundle.js',
    //path 設置打包完成後文件輸出路徑
    path: path.resolve(__dirname,'dist')
  },
  module: {
    rules:[
      {
        test: /\.(png|jpg|gif)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]', //對打包後的圖片命名
            outputPath: 'images/' //打包後圖片輸出的位置 dist\images
          }
        }
      }
    ]
  }
}

在src目錄下新建images文件夾,存放圖片

修改index.js

//index.js

//import導入圖片
import image from './images/11.png'

let img=new Image()
img.src=image
document.body.append(img)

運行npm run build後的目錄結構以下

demo1
├── demo1/dist
│   ├── demo1/dist/bundle.js
│   ├── demo1/dist/images
│   │   └── demo1/dist/images/11.png
│   └── demo1/dist/index.html
├── demo1/package.json
├── demo1/package-lock.json
├── demo1/src
│   ├── demo1/src/images
│   │   └── demo1/src/images/11.png
│   └── demo1/src/index.js
└── demo1/webpack.config.js

在dist目錄下出現了images目錄和圖片,建立index.html,引入js文件,在瀏覽器中打開就能夠看到圖片。

url-loader的使用
url-loader安裝
npm i url-loader -D

url-loader的做用跟'file-loader'的做用很相似

webpack.config.js

module: {
    rules:[
     {
        test: /\.(png|jpg|gif)$/,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name].[ext]', //對打包後的圖片命名
            outputPath: 'images/', //打包後圖片放的位置 dist\images
            limit: 20480
            //1024 == 1kb  
            //小於20kb時打包成base64編碼的圖片不然單獨打包成圖片
          }
        }
      }
    ]
  }
}

limit屬性:當圖片大小大於屬性值時打包成圖片輸出到images目錄下,不然打包成base64編碼的圖片注入bundle.js中

由於base64編碼的圖片致使打包文件變大,因此圖片比較小時打包成base64編碼的圖片,圖片比較大時單獨打包成一張圖片。

cssscss的打包

安裝相應的loader
npm i css-loader style-loader -D
npm i node-sass sass-loader -D
npm i postcss-loader -D
npm i autoprefixer -D

postcss-loaderautoprefixer配合使用能夠在打包過程當中自動添加前綴

在demo1根目錄下新建postcss.config.js,配置以下

//postcss.config.js
module.exports={
  plugins: [
    require('autoprefixer')
  ]
}

在webpack.config.js文件的`module.rules'數組中添加配置

module:{
  rules:[
    {
      test: /\.css$/,
       use:[
         'style-loader',
         'css-loader',
         'postcss-loader'  
         //加前綴  npm i autoprefixer -D
         //在項目根目錄下配置postcss.config.js文件
       ]
     },
     {
        test: /\.scss$/,
        use:[
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              //importLoaders
              //用於配置css-loader做用於@import的資源以前有多少個loader先做用於@import的資源
            }
          },
          'postcss-loader',
          'sass-loader'
        ]
      }
  ]
}

demo1src下新建css文件夾,在css文件夾下新建style.cssindex.scss文件。

index.scss

body{
  border: 1px solid red;
  width: 300px;
  height: 300px;
  img{
    width: 100px;
    height: 100px;
    border-radius: 10%;
    transform: translate(100px,100px);
  }
}

style.css

body{
  border-radius: 10%;
}

index.js

//index.js

import image from './images/11.png'
import './style.css'
import './index.scss'

let img=new Image()
img.src=image
document.body.append(img)

運行npm run build,在dist目錄下新建index.html,引入js文件,在瀏覽器中打開就能夠看到效果,說明打包成功。

css模塊化

css模塊化,避免頁面樣式之間相互影響
webpack.config.js中的css-loader添加modules: true

//webpack.config.js

module:{
  rules: [
      {
        test: /\.css$/,
        use:[
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          },
          'postcss-loader'  
          //加前綴  npm i autoprefixer -D
          //在項目根目錄下配置postcss.config.js文件
        ]
      },
      {
        test: /\.scss$/,
        use:[
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              //importLoaders
              //用於配置css-loader做用於@import的資源以前有多少個loader先做用於@import的資源
              modules: true //加載css模塊化打包,避免樣式文件之間相互影響
            }
          },
          'postcss-loader',
          'sass-loader'
        ]
      }
  ]
}

修改index.js
.img是類名須要在樣式文件中提早寫好樣式

//index.js

import image from './images/11.png'
import style from './css/style.css'
// import style from './css/index.scss'

let img=new Image()
img.src=image

//style.img .img是scss文件中寫好的類名
img.classList.add(style.img)

document.body.append(img)

index.scss

body{
  border: 1px solid red;
  width: 300px;
  height: 300px;
  img{
    width: 100px;
    height: 100px;
    border-radius: 10%;
    transform: translate(100px,100px);
  }
  .img{
    border: 10px solid royalblue;
  }
}

style.css

body{
  border-radius: 10%;
}
body .img{
  border: 10px solid yellow;
}

結果

能夠看到添加了一個class,類名是一串比較複雜的字符串,從而避免這個樣式對別的元素產生影響。


2、進一步配置webpack4,使本身在學習webpack4的時候更方便

這一部分主要是學會使用html-webpack-pluginclean-webpack-plugin插件,主要是學會配置devServer以及使用webpack的熱模塊替換功能。

首先,在webpack4目錄下新建demo2文件夾將demo1目錄下的全部東西複製到demo2

在上一部分咱們都是手動在dist目錄下建立index.html引入js文件查看打包結果,這樣會很麻煩。咱們可使用html-webpack-plugin來自動生產index.html,而且可以自動引入打包好的文件,直接打開生產的html就能夠看到打包結構。

1.html-webpack-plugin的使用

安裝
npm i html-webpack-plugin -D

webpack.config.js中配置plugins配置項

const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')

module.exports={
  //mode development: 開發環境 production:生產環境
  mode: 'development', 
  //entry 入口文件配置  
  entry: {
    index: './src/index.js'
  },
  //打包完成後文件輸出位置配置
  output: {
    //filename 設置打包後文件的名字
    //若是不設置filename,則文件的名字跟入口文件路徑的屬性名同樣
    filename: 'bundle.js',
    //path 設置打包完成後文件輸出路徑
    path: path.resolve(__dirname,'dist')
  },
  module: { },
  plugins: [
    new htmlWebpackPlugin({
      template: './index.html'
    })
  ]
}

在demo2目錄下新建index.html做爲模板

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>模板</title>
</head>
<body>
  <div id="root"></div>
<script type="text/javascript" src="bundle.js"></script></body>
</html>

運行npm run build,能夠看到dist目錄下自動生產index.html,而且還自動引入js文件

2.clean-webpack-plugin的使用

每次打包生成的dist目錄,若是改一次代碼,都得要刪除一次dist目錄,這樣很麻煩,能夠經過clean-webpack-plugin在每次打包的前自動清空dist目錄。

安裝
npm i clean-webpack-plugin -D

webpack.config.jsplugins中配置以下

const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
const cleanWebpackPlugin = require('clean-webpack-plugin')

module.exports={
  //mode development: 開發環境 production:生產環境
  mode: 'development', 
  //entry 入口文件配置  
  entry: {
    index: './src/index.js'
  },
  //打包完成後文件輸出位置配置
  output: {
    //filename 設置打包後文件的名字
    //若是不設置filename,則文件的名字跟入口文件路徑的屬性名同樣
    filename: 'bundle.js',
    //path 設置打包完成後文件輸出路徑
    path: path.resolve(__dirname,'dist')
  },
  module: { },
  plugins: [
    new htmlWebpackPlugin({
      template: './index.html'
    }),
   new cleanWebpackPlugin()
  ]
}

運行npm run build,能夠本身測試,每次打包前都會把dist目錄下的文件刪掉。

3.entryoutput多入口配置

module.exports={
  //mode development: 開發環境 production:生產環境
  mode: 'development', 
  //entry 入口文件配置  
  entry: {
    index: './src/index.js',
    main: './src/index.js'
  },
  //打包完成後文件輸出位置配置
  output: {
    //filename 設置打包後文件的名字
    //若是不設置filename,則文件的名字跟入口文件路徑的屬性名同樣
    // 佔位符
    filename: '[name].js',
    //path 設置打包完成後文件輸出路徑
    path: path.resolve(__dirname,'dist')
  },
}

當有多入口的時候,須要修改filename的屬性值爲'[name].js'

運行npm run build,就會在dist目錄下生產index.jsmain.js

4.配置devtool

devtool決定源代碼與打包後的代碼之間的映射關係,方便對代碼進行調試。

開發環境推薦: cheap-module-eval-source-map
生產環境推薦: cheap-module-source-map

devtool具體內容請查閱:文檔:devtool

module.exports={
  devtool: 'cheap-module-eval-source-map',
  //開發環境推薦: cheap-module-eval-source-map
  //生產環境推薦: cheap-module-source-map
}

5.配置devServer

文檔:devServer

安裝 webpack-dev-server
npm i webpack-dev-server -D
在webpack.config.js中添加如下內容
module.exports={
  devServer: {
    contentBase: './dist',
    // open: true, //自動打開瀏覽器
    // port: 8080, //默認8080
  }
}

修改package.jsonscript,添加 "start": "webpack-dev-server"

"scripts": {
    "start": "webpack-dev-server"
  },

執行npm run start後打開瀏覽器就能夠看到效果,當咱們修改代碼的時候頁面就會從新刷新。

有時咱們但願頁面只更新咱們修改的那一部分就能夠了,而並非刷新頁面,因此須要啓用webpack的熱模塊替換功能。

 6.啓用webpack的熱模塊替換功能

首先修改index.js
import './css/style.css'

var btn = document.createElement('button')
btn.innerHTML='新增'
document.body.appendChild(btn)

btn.onclick=function(){
  var div=document.createElement('div')
  div.innerHTML='items'
  document.body.appendChild(div)
}
修改style.css,刪掉index.scss
//style.css
body{
  background: yellow;
}
div:nth-of-type(odd){
  background: chartreuse;
  font-size: 18px;
}
在webpack.config.js中

引入webpack:const webpack=require('webpack')
添加內容以下:

const webpack=require('webpack')
module.exports={
  plugins: [
    new webpack.HotModuleReplacementPlugin() //啓用HMR
  ],
  devServer: {
    contentBase: './dist',
    // open: true, //自動打開瀏覽器
    // port: 8080,
    hot: true, //啓用webpack的熱模塊替換功能
    hotOnly: true 
    //devServer.hot在沒有頁面刷新的狀況下啓用熱模塊替換做爲構建失敗時的後備
  }
}

hot:true啓用HotModuleReplacementPlugin(HMR)

執行npm run start,在瀏覽器打開之後,修改div的背景顏色,只有改變的地方纔發生變化,可是頁面並無刷新。

在demo2的src目錄下新建number.js

number.js
var number=function(){
  var div=document.createElement('div')
  div.setAttribute("id","number")
  div.innerHTML=103
  document.body.appendChild(div)
}

export default number

修改index.js

import number from './number'
number()

運行npm run start,在瀏覽器中打開看結果,而後在number.js中修改內容,可是頁面並無顯示修改後的內容

這是由於在引入js文件的時候,熱模塊替換的實現方式有點區別。

js要達到熱模塊替換的效果,得要if(module.hot){}這一部分代碼,不然就算改了代碼,頁面不刷新,修改的地方在頁面上頁面變化。

css樣式由於css-loader已經實現if(module.hot){}這一部分,因此不須要單獨實現這一部分。

再次修改index.js

import number from './number'
number()

if(module.hot){
  module.hot.accept('./number.js',function(){
    number()
    document.body.removeChild(document.getElementById('number'))
  })
}

運行npm run start,在瀏覽器中打開看結果,而後在number.js中修改內容,發現頁面顯示修改後的內容


3、使用Babel處理js文件

Babel是一個普遍使用的轉碼器,能夠將ES6代碼轉爲ES5代碼,從而在現有環境執行。

Babel總共分爲三個階段:解析(parse),轉換(transform),生成(generate)。

Babel自己不具備任何轉化功能,它把轉化的功能都分解到一個個plugin裏面。所以當咱們不配置任何插件時,通過Babel輸出的代碼和輸入是相同的。

Babel插件的使用

  1. 將插件的名字增長到配置文件中:項目根目錄下建立.babelrc配置文件或是webapck.config.js中配置,通常都是在.babelrc中配置。
  2. 使用 npm install xxx 進行安裝

Babel的配置文件是.babelrc,存放在項目的根目錄下。使用Babel的第一步,就是配置這個文件。

該文件用來設置轉碼規則和插件,基本格式以下。

{
  "presets": [],
  "plugins": []
}

Babel簡單介紹

preset

preset(預設)就是一系列插件的集合
@babel/preset-env包含全部ES6轉譯爲ES5的插件集合

core-js

轉換一些內置類(Promise, Symbols等等)和靜態方法(Array.from等)。

@babel/core

是做爲Babel的核心存在,Babel的核心api都在這個模塊裏面。

babel-loader

babel-loader在webpack中使用,是webpack和Babel之間的通信橋樑

@babel/polyfill介紹

@babel/preset-env默認只轉譯js語法,而不轉譯新的API,好比Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局對象,以及一些定義在全局對象上的方法(好比Object.assign)都不會轉譯。這時就必須使用@babel/polyfill(內部集成了core-jsregenerator)。

使用時,在全部代碼運行以前增長import "@babel/polyfill"

或者是在webpack.config.js入口配置

module.exports = {
  entry: ["@babel/polyfill", "./app/js"],
}

所以必須把@babel/polyfill做爲dependencies而不是devDependencies

@babel/polyfill主要有兩個缺點:

1.使用@babel/polyfill須要作些額外配置,實現打包的時候按需引入,不然會把@babel/polyfill所有注入代碼中會致使打出來的包很是大。

2.@babel/polyfill會污染全局變量。

Babel7的一個重大變化就是npm package 名稱的變化,把全部babel-*重命名爲@babel/*,例如:

  • babel-polyfill重命名爲@babel/polyfill
  • babel-preset-env重命名爲@babel/preset-env

Babel在webpack中的用法

首先實現對ES6語法的轉譯

在webpack4目錄下新建demo3文件夾,將demo2目錄下的全部東西複製到demo3中

安裝babel-loader、 @babel/core、@babel/preset-env

  • npm i babel-loader -D
  • npm i @babel/core -D
  • npm i @babel/preset-env -D

babel-loader@8須要安裝@babel/core7.x版本。

在webpack.config.js配置

module.exports={
  module: {
    rules:[
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use:{
          loader: 'babel-loader',         
          options:{
            presets: [
              ["@babel/preset-env",{
                //targets:表示編譯出的代碼想要支持的瀏覽器版本
                targets: {
                  chrome: "67"                 
                }
              }]
            ]
          }
        }
      }
    ]
  }
}

執行npm run buildnpx webpack就能夠看到dist目錄下的打包文件,可是隻是將ES6的語法進行轉譯,並無對ES6新API進行轉譯,因此咱們須要配置@babel/polyfill解決這個問題。

安裝 @babel/polyfill
npm i @babel/polyfill --save

index.js中引入@babel/polyfill

index.js

//index.js

import '@babel/polyfill'

let arr=[
  new Promise(()=>{}),
  new Promise(()=>{}),
  2
]

arr.map((item)=>{
  console.log(item)
})

引入@babel/polyfill前,main.js的大小爲29.5KB

引入@babel/polyfill後,main.js的大小爲1MB

注意:以上對比都是在沒有targets這個選項的狀況下,由於有些瀏覽器幾乎都支持ES6,在這種狀況下,@babel/preset-env將不會對代碼進行處理。

這是由於把@babel/polyfill對全部API的實現都注入到打包文件中,可是裏面不少的API咱們在代碼中並無用到,因此須要修改配置,按需引入對應的API。

修改webpack.config.js配置

添加"useBuiltIns": "usage"之後,須要安裝core-js@2,而且添加"corejs": 2配置項,這時配置選項比較多,須要在項目根目錄下新建.babelrc文件,在這個文件中配置。

.babelrc配置以下:

  • "useBuiltIns"屬性值爲"usage"時,會自動引入@babel/polyfill,必須保證已經安裝了@babel/polyfill
  • "useBuiltIns"屬性值爲"usage"時,須要添加"corejs": 2配置項,不然報錯,須要安裝core-js

首先刪掉index.js中的import '@babel/polyfill'

安裝 core-js
npm i --save core-js@2npm i --save core-js@3
{
  "presets": [["@babel/preset-env",{
    "useBuiltIns": "usage", //不須要把polly都打包到代碼中,根據代碼按需轉譯
    // core-js@3和core-js@2二選一
    //"corejs": 3,  //npm i --save core-js@3
    "corejs": 2  //npm i --save core-js@2
  }]]
}

修改webpack.config.js,刪除options對象

module.exports={
  module: {
    rules:[
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  }
}

執行npm run build,打包後的文件大小爲165KB

可是,在開發類庫或是第三方模塊時不適合使用@babel/polyfill,因此接下來使用@babel/plugin-transform-runtime來解決這個問題。

@babel/plugin-transform-runtime、@babel/runtime和@babel/runtime-corejs2的用法

@babel/runtime-corejs2:是一個包含Babel modular runtime helpersregenerator-runtime以及core-js的庫。

@babel/runtime:是一個包含Babel modular runtime helpersregenerator-runtime的庫。

在配置項中corejs屬性值爲默認爲false,若是須要將PromiseAPI進行轉譯,則須要設置屬性值爲2時,而且安裝@babel/runtime-corejs2

安裝:
  • npm i @babel/plugin-transform-runtime -D
  • npm i --save @babel/runtime
  • npm i --save @babel/runtime-corejs2

修改.babelrc文件

{
  "plugins": [
    ["@babel/plugin-transform-runtime",{
      "helpers": true,
      "regenerator": true,
      "useESModules": false,
      "corejs": 2
    }]
  ]
}

咱們把presets配置項去掉了,而後npm run build打包,打開打包後的main.js查看,雖然把轉譯了Promise,可是ES6新語法並沒被轉譯,例如:let沒有被轉譯爲var

因此仍是須要配置presets,由於"@babel/preset-env"包含了對全部ES6語法轉譯爲ES5插件。

再次修改.babelrc文件

{
 "presets": ["@babel/preset-env"],
  "plugins": [
    ["@babel/plugin-transform-runtime",{
      "helpers": true,
      "regenerator": true,
      "useESModules": false,
      "corejs": 2
    }]
  ]
}

添加presets配置項,而後npm run build打包,打開打包後的main.js查看,能夠看到let和箭頭函數都被轉譯爲ES5語法了。 


4、Tree Shaking使用

首先,在webpack4目錄下新建demo4文件夾,將demo3目錄下的全部東西複製到demo4中

Tree Shaking能夠用來剔除JavaScript中用不上的死代碼。它依賴靜態的ES6模塊化語法,例如經過importexport導入導出。

須要注意的是要讓Tree Shaking正常工做的前提是JavaScript代碼必須採用ES6模塊化語法,由於ES6模塊化語法是靜態的,這讓Webpack能夠簡單的分析出哪些export的被import過了。

接下來配置WebpackTree Shaking生效

webpack4默認保留ES6模塊化語句,並無經過Babel將其轉換
修改.babelrc文件爲以下:

//.babelrc

{
   "presets": [["@babel/preset-env",{
      "useBuiltIns": "usage",
      "corejs": 2,
      "modules":false //關閉 Babel 的模塊轉換功能,保留本來的 ES6 模塊化語法
      //默認是auto,取值還能夠是 amd, umd, systemjs, commonjs,auto等
   }]]
}

修改webapck.config.js,添加

optimization: {
  usedExports: true
}

module.exports{}

module.exports={
 mode: 'development',
  optimization: {
  //開發壞境使用tree shaking時加usedExports: true
    usedExports: true 
  },
}

還需經過package.json"sideEffects"屬性來告訴webpack哪些模塊是能夠忽略掉,若是沒有則設置爲false,來告知webpack,它能夠安全地刪除未用到的export

修改package.json

{
  "name": "your-project",
  "sideEffects": false
}

在demo4下的src新建math.js

index.js

//tree shaking import export
import {cube} from './math.js'

let component = () => {
  let element = document.createElement('pre')
  element.innerHTML = [
    'Hello webpack!',
    '2 cubed is equal to ' + cube(2)
  ].join('\n\n');
  console.log(cube)
  
  return element;
}
document.body.appendChild(component());

math.js

export let square= (x) => {
  console.log(x)
  return x * x;
}

export let cube = (x) => {
  console.log(x)
  return x * x * x;
}

運行npm run build,而後打開打包後的js文件:main.js找到下面這段文字

/*!*********************!*\
   !*** ./src/math.js ***!
   \*********************/
 /*! exports provided: square, cube */
 /*! exports used: cube */
 /***/

從上面這段文字能夠看出Tree Shaking生效了,可是在開發環境下,並無把沒有用的代碼刪掉,由於在開發環境下還須要對代碼進行調試。

咱們已經找出須要刪除的「未引用代碼(dead code)」,然而,不只僅是要找出,還要刪除它們。爲此,咱們須要將mode配置選項設置爲production,將optimization對象刪掉,修改devtool配置選項

webpack.config.js

module.exports = {
  mode: 'production',
  devtool: 'cheap-module-source-map'
}

運行npm run build,查看打包結果就能夠看到沒有用的代碼被刪掉了。


5、DevelomentProduction不一樣環境的配置

在webpack4下新建demo5,將demo4下的全部文件複製到demo5中

由於在不一樣的環境下,webpack的配置稍微有點區別,若是咱們須要在不一樣的換將下切換,就得修改webpack配置,這是很麻煩並且還容易改錯,因此咱們須要把配置文件進行拆分。

在項目根目錄下新建build文件夾,而後在build文件夾中新建webpack.dev.jswebpack.prod.jswebpack.base.js三個文件

webpack.dev.js:是開發環境
webpack.prod.js:是生產環境
webpack.base.js:是開發環境和生產環境都用到的配置

這幾個文件之間的結合靠'webpack-merge'這個插件。

安裝
npm i webpack-merge -D

webpack.dev.js

//webpack.dev.js

const webpack=require('webpack')
const merge = require('webpack-merge')
const baseConfig=require('./webpack.base')

const devConfig={
  mode: 'development', 
  devtool: 'cheap-module-eval-source-map',
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ],
  optimization: {
    usedExports: true
  },
  devServer: {
    contentBase: './dist',
    // open: true, //自動打開瀏覽器
    // port: 8080,
    hot: true, //啓用webpack的熱模塊替換功能
    //hotOnly: true 
    //devServer.hot在沒有頁面刷新的狀況下啓用熱模塊替換做爲構建失敗時的後備
  }
}

module.exports=merge(baseConfig,devConfig)

webapck.prod.js

//webapck.prod.js

const merge = require('webpack-merge')
const baseConfig=require('./webpack.base')

const prodConfig={
  mode: 'production', 
  devtool: 'cheap-module-source-map'
}

module.exports=merge(baseConfig,prodConfig)

可是這兩個文件還有大量重複的代碼,新建webpack.base.js

//webpack.base.js

const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
const cleanWebpackPlugin = require('clean-webpack-plugin')

module.exports={
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname,'dist')
  },
  module: {
    rules:[
      {
        test: /\.(png|jpg|gif)$/,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name].[ext]', 
            outputPath: 'images/', 
            limit: 2048           
          }
        }
      },
      {
        test: /\.css$/,
        use:[
          'style-loader',
          'css-loader',
          'postcss-loader' 
        ]
      },
      {
        test: /\.scss$/,
        use:[
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              modules: true 
            }
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },
  plugins: [
    new htmlWebpackPlugin({
      template: './index.html'
    }),
    new cleanWebpackPlugin(),
  ]
}

修改package.jsonscript:

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

開發環境:運行npm run dev,打開瀏覽器訪問http://localhost:8080/就能夠看到結果
生產環境:運行npm run build


6、SplitChunksPlugin插件的用法

1.SplitChunksPlugin插件介紹

webpack 4移除CommonsChunkPlugin,取而代之的是SplitChunksPlugin。下面介紹SplitChunksPlugin的用法。

webpack4目錄下新建demo6目錄,將demo5目錄下的全部文件都複製到demo6中。

安裝 lodash

npm i lodash --save

package.json添加

"scripts": {
    "dev-build": "webpack --config ./build/webpack.dev.js"
  }

修改index.js

import _ from 'lodash'
console.log(_.join(['lodash', 'babel', 'webpack'], '-'))

運行npm run dev-build後,demo6目錄結構以下

demo6
├── demo6/build
│   ├── demo6/build/webpack.base.js
│   ├── demo6/build/webpack.dev.js
│   └── demo6/build/webpack.prod.js
├── demo6/dist
│   ├── demo6/dist/index.html
│   └── demo6/dist/main.js
├── demo6/index.html
├── demo6/package.json
├── demo6/package-lock.json
├── demo6/postcss.config.js
└── demo6/src
    └── demo6/src/index.js

dist目錄下只有main.js一個js文件,lodash庫也一塊兒打包到main.js裏面,這種狀況下會致使文件變大,致使頁面加載速度變慢,咱們須要把第三庫或是須要單獨打包的代碼給分割出來。

這時須要使用webpack4自帶的插件SplitChunksPlugin,默認狀況下它將只會影響按需加載的代碼塊

webpack.config.js添加optimization.splitChunks.chunks

optimization: {
   splitChunks: {
    //chunks: all, async, initial.
    //async針對異步加載的chunk作切割,initial針對初始chunk,all針對全部chunk。
     chunks: 'async'
   }
}

運行npm run dev-build後,打包後的代碼並無分割。

修改optimization.splitChunks.chunksall

optimization: {
   splitChunks: {
    //chunks: all, async, initial.
    //async針對異步加載的chunk作切割,initial針對初始chunk,all針對全部chunk。
     chunks: 'all'
   }
}

運行npm run dev-build後,demo6目錄結構以下

demo6
├── demo6/build
│   ├── demo6/build/webpack.base.js
│   ├── demo6/build/webpack.dev.js
│   └── demo6/build/webpack.prod.js
├── demo6/dist
│   ├── demo6/dist/index.html
│   ├── demo6/dist/main.js
│   └── demo6/dist/vendors~main.js
├── demo6/index.html
├── demo6/package.json
├── demo6/package-lock.json
├── demo6/postcss.config.js
└── demo6/src
    └── demo6/src/index.js

能夠看到dist目錄下多了vendors~main.js文件,說明SplitChunksPlugin插件生效了

接下來先看optimization.splitChunks的默認配置

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
}
  • chunks: 表示將對哪些塊進行優化,可選async, initial, all,async對異步加載的模塊進行分割,initial對初始模塊,all對全部模塊
  • minSize: 加載的模塊不小於30kb才進行分割
  • minChunks: 生成的chunk,共享該模塊的chunk必須不小於1時才分割
  • maxAsyncRequests:按需加載時的最大並行請求數
  • maxInitialRequests:入口處的最大並行請求數
  • automaticNameDelimiter:默認狀況下,webpack將使用塊的名稱和名稱生成名稱(例如vendors~main.js)。此選項指定用於生成的名稱的分隔符
  • name:生成chunk的名字,若是設成true,將根據模塊和緩存組配置結合生成名稱
  • cacheGroups: 緩存組能夠繼承和/或覆蓋任何選項splitChunks.*; 可是test,priority而且reuseExistingChunk只能在高速緩存組級別配置。要禁用任何默認緩存組,請將其設置爲false。
  • test:控制此緩存組選擇的模塊
  • priority:模塊能夠屬於多個緩存組,模塊則歸於緩存組priority高的
  • reuseExistingChunk: 若是當前塊包含已拆分的模塊,則將重用它而不是生成新的塊。

從以上能夠看出來,默認的配置只對異步加載的模塊有效

修改index.js,異步加載lodash

function getComponent(){
  return import('lodash').then(({default: _})=>{
    var element=document.createElement('div')
    element.innerHTML=_.join(['lodash', 'babel', 'webpack'], '-')
    return element
  })
}

getComponent().then(element=>{
  document.body.appendChild(element)
})

這時運行npm run dev-build會報錯,須要下載安裝 @babel/plugin-syntax-dynamic-import
npm i @babel/plugin-syntax-dynamic-import -D

在.babelrc中添加

"plugins": ["@babel/plugin-syntax-dynamic-import"]

再次運行npm run dev-build,此時打包成功,目錄結構以下

demo6
├── demo6/build
│   ├── demo6/build/webpack.base.js
│   ├── demo6/build/webpack.dev.js
│   └── demo6/build/webpack.prod.js
├── demo6/dist
│   ├── demo6/dist/0.js
│   ├── demo6/dist/index.html
│   └── demo6/dist/main.js
├── demo6/index.html
├── demo6/package.json
├── demo6/package-lock.json
├── demo6/postcss.config.js
└── demo6/src
    └── demo6/src/index.js

能夠看到dist目錄下0.js,就是對lodash打包後的文件,有時咱們但願可以改變0.js的名字

修改index.js,添加/* webpackChunkName:"lodash" */

function getComponent(){
  return import(/* webpackChunkName:"lodash" */'lodash').then(({default: _})=>{
    var element=document.createElement('div')
    element.innerHTML=_.join(['lodash', 'babel', 'webpack'], '-')
    return element
  })
}

getComponent().then(element=>{
  document.body.appendChild(element)
})

運行npm run dev-build,發現0.js變爲vendors~lodash.js

也能夠經過設置optimization.splitChunks.cacheGroups.vendors.name來修改打包後的文件名字

修改optimization.splitChunks配置

optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 30000,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name: 'vendors'
        },
        default: {
          minChunks: 1,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  },

運行npm run dev-build,發現打包後的文件名字變爲vendors.js

以上就是SplitChunksPlugin的簡單用法

詳情請訪問官網


7、css代碼的分割

MiniCssExtractPlugin插件將CSS提取到單獨的文件中。它爲每一個包含CSS的JS文件建立一個CSS文件,在webpack 4才能使用

在webpack目錄下新建demo7,將demo6下的全部文件都複製到demo7中,進入demo7

安裝
npm install --save-dev mini-css-extract-plugin

官網提示,這個插件應該在生產環境中使用,因此修改webpack的相關配置。

首先將webpack.base.js中對css和scss處理的loader配置分別複製粘貼(在webpack.base.js中刪掉這一部分)到webpack.prod.jswebpack.dev.js中。

webpack.prod.js中引入
const miniCssExtractPlugin = require('mini-css-extract-plugin')
修改後的相關配置以下:

webpack.base.js
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
const cleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
  entry: {
    main: './src/index.js' //對應filename
    //入口文件引入的模塊,分割打包的名字對應chunkFilename
  },
  output: {
    filename: '[name].js', 
    chunkFilename: '[name].chunk.js',
    path: path.resolve(__dirname, '../dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },

  plugins: [
    new htmlWebpackPlugin({
      template: './index.html'
    }),
    new cleanWebpackPlugin(),
  ],
  optimization: {
    usedExports: true,
    splitChunks: {
      //chunks: all, async, initial.
      //async 只對異步分割有效
      // initial 同步
      //all 要配置cacheGroups
      chunks: 'all',
      // 引入的包或是庫大於30kb纔對代碼進行分割
      minSize: 30000,

      maxSize: 0, //沒多大意義
      minChunks: 1, //當一個模塊至少引入多少次時纔會進行代碼分割
      maxAsyncRequests: 5, //同時加載的模塊數最可能是5個
      maxInitialRequests: 3,
      automaticNameDelimiter: '~', //打包後的文件名字之間的鏈接符
      name: true,
      cacheGroups: { //緩存組
        // vendors: false,
        vendors: { 
          //同步 檢查是否在node_modules裏面
          test: /[\\/]node_modules[\\/]/,
          priority: -10, //優先級
          name: 'vendors'
        },
        // default: false
        default: {
          minChunks: 1,
          priority: -20,
          reuseExistingChunk: true, //若是模塊已經打包過了就引用以前打包好的模塊
          // filename: 'common.js'
        }
      }
    }
  }
}
webpack.prod.js
const merge = require('webpack-merge')
const baseConfig=require('./webpack.base')
const miniCssExtractPlugin = require('mini-css-extract-plugin')

const prodConfig={
  mode: 'production', 
  devtool: 'cheap-module-source-map',
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          miniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.scss$/,
        use: [
          miniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              modules: true
            }
          },
          'postcss-loader',
          'sass-loader'
        ]
      },
    ]
  },
 
  plugins: [
    new miniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[name].chunk.css'
    })
  ]
}

module.exports=merge(baseConfig,prodConfig)
webapck.dev.js
const webpack=require('webpack')
const merge = require('webpack-merge')
const baseConfig=require('./webpack.base')

const devConfig={
  mode: 'development', 
  // devtool: 'cheap-module-eval-source-map',
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              modules: true
            }
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
    ]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ],
  
  devServer: {
    contentBase: './dist',
    // open: true, //自動打開瀏覽器
    // port: 8080,
    hot: true, //啓用webpack的熱模塊替換功能
    //hotOnly: true 
    //devServer.hot在沒有頁面刷新的狀況下啓用熱模塊替換做爲構建失敗時的後備
  }
}

module.exports=merge(baseConfig,devConfig)

須要注意的一點就是,在前面的時候,咱們配置tree shaking的時候,在package.json添加了sideEffects配置項,須要修改這個配置項爲

"sideEffects": [
    "*.css"
  ],

不然經過import './*.css'方式引入的css文件會被刪除掉。

運行npm run build,咱們就能夠看到dist目錄下,css文件被單獨分割出來了。

官網還提供一個插件能夠對css文件進行壓縮

安裝
npm i optimize-css-assets-webpack-plugin -D
修改webpack.prod.js
//引入
const optimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
//添加
optimization: {
    minimizer: [new optimizeCSSAssetsPlugin({})],
  },

再次運行npm run build,打開打包後的css文件,就發現css文件被壓縮了。

更具體的使用方法請看官網


代碼都已經上傳到github:github:webpack4

相關文章
相關標籤/搜索