1. 程式人生 > >基於webpack4.0手動搭建web專案(更新中)

基於webpack4.0手動搭建web專案(更新中)

閒暇之餘做的總結,仍在更新中,有錯誤或不足的地方請大家指正與分享見解,謝謝。

一.準備工作

1.初始化專案目錄:npm init -y

2.建立以下的目錄結構:

--dist

--src

----index.js

--package.json(初始化後生成)

3.完成基本的webpack配置:

首先,安裝基本的webpack包:

npm install --save-dev webpack webpack-cli

接著,進行簡單的webpack配置:

在根目錄下建立webpack.config.js:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

4.在package.json檔案的scripts屬性下配置npm指令碼:

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

5.這個時候我們只要在終端輸入npm run build就可以對src目錄下的index.js檔案打包,在dist目錄下生成bundle.js檔案。

二.ES6的babel轉碼配置

1.安裝babel-preset-env包:npm install –save-dev babel-preset-env

2.在根目錄下建立.babelrc檔案:

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

3.為了檢驗我們的配置是否成功,我們再安裝babel-cli包:npm install --save-dev babel-cli,然後我們在根目錄下建立一個babel_test檔案用於測試,如下:

--babel_test

----test.js

test.js:

let arr = [1, 2, 3];
console.log([...arr]);

並在package.json檔案中新增npm指令碼:

"scripts": {
 ...
 "build": "webpack",
 "babel": "babel ./babel_test/test.js -o ./babel_test/res.js"
}

該指令碼的作用就是將test.js進行轉碼,並將轉碼後的內容儲存到res.js檔案中。

在終端執行npm run babel則會在相同目錄下生成res.js:

"use strict";

 
var arr = [1, 2, 3];
console.log([].concat(arr));

該檔案已經轉碼成功,說明我們的配置是正確的。

三.安裝簡單的loader解析

webpack中,有一種操作就是在“.js”檔案中引入非javascript資源,所以在將其打包的過程中,我們需要某些loader解析器對相關的資源進行解析。

首先我們先來看看引入css資源:

安裝style-loader和css-loader兩個loader:npm install --save-dev style-loader css-loader

webpack.config.js:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': './src'
    }
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      }
    ]
  }
}

這個時候我們就可以往入口檔案(index.js)中import './style.css'。現在,當該模組執行時,含有css字串的<style>標籤,將被插到html檔案的<head>中。廢話不多說,實慄說話:

/src/style.css:

.hello {
  color: red;
}
 

/src/index.js

import './style.css'

我們對index.js進行打包:npm run build,便在dist目錄下產生了bundle.js

為了驗證我們的樣式是否已經打包成功,我們在dist目錄下建立一個index.html檔案,並將bundle.js檔案引入進來:
/dist/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>webpack_all</title>
</head>
<body>
  <p class="hello">hello world</p>
  <script src="./bundle.js"></script>
</body>
</html>

這個時候我們看到在頁面上呈現紅色字型的“hello world”:


小夥伴可能會說:“這不對啊,明明之前說css檔案中的樣式將會通過style標籤插入到html文字中,但是上面的index.html只是將bundle.js插入進去而已。”

別急,上圖說話:


看到沒有,當我們在瀏覽器中開啟該index.html檔案,則會發現包含內容的style標籤已經被插入到頁面中了。也就是說,通過style-loader和css-loader對入口檔案進行打包之後,我們可以通過在頁面中引入bundle.js的方式通過bundle.js來插入style標籤。(之後有時間想研究一下其中的原理,小夥伴們有知道的可以分享一下哦,想想大概也就是通過js來建立style標籤,然後插入)

那如果我們想要匯入圖片呢?這個時候我們就可以用file-loader。

npm install --save-dev file-loader

webpack.config.js:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': './src'
    }
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader'
        ]
      }
    ]
  }
}
 

現在,當你 import MyImage from './my-image.png',該影象將被處理並新增到 output 目錄,並且_ MyImage 變數將包含該影象在處理後的最終 url。當使用 css-loader 時,如上所示,你的 CSS 中的 url('./my-image.png') 會使用類似的過程去處理。loader 會識別這是一個本地檔案,並將 './my-image.png' 路徑,替換為輸出目錄中影象的最終路徑。html-loader 以相同的方式處理 <img src="./my-image.png" />。

現在我們向專案中新增一個影象:

現在我們在/src目錄下面新增一張圖片:

/src/Hydrangeas.jpg:

在index.js中匯入該圖片:

import Icon from './Hydrangeas.jpg'

現在我們只是將圖片匯入進來並沒有將它使用,故而我們還要新增以下的程式碼:

function component() {
  let element = document.createElement('div');

   // 將影象新增到我們現有的 div。
   let myIcon = new Image();
   myIcon.src = Icon;

   element.appendChild(myIcon);

  return element;
}

document.body.appendChild(component());

然後打包:npm run build

開啟index.html,呈現以下的頁面:


實際上,在打包的過程中我們也把該圖片打包到出口目錄中,如下:


只不過圖片的名字已經改成了隨機串,同時頁面中引用的圖片也是打包後的這張圖片,如下:

這樣來說,其實我們也就可以在頁面中引用這張圖片,不過有一個問題:我們每次打包完之後圖片的名稱都不一樣,也就說如果我們需要在頁面中直接引用該圖片,我們就需要每次改變在頁面中引用的圖片名,這是特別麻煩的一件事。有了問題就需要解決,不過這個問題我們把它放到講plugin(外掛)的時候再來解決。

現在我們已經將普遍會用到的兩種基本的資源通過loader來進行相應的解析操作,但是各位小夥伴們可能沒有發現,就是我們之前配置的babel轉碼在webpack中有用到嗎?對於這個問題,我們先來看看被打包後的檔案bundle.js:


從上面的圖片可以看到有Symbol的字串,小夥伴們會很肯定地覺得webpack打包的時候並沒有使用我們之前的配置對程式碼進行相應的轉碼。不過我們不應該這樣看,因為我們目前的配置確實不能將Symbol進行轉碼,要對它進行轉碼還需要配置其他的一些外掛。不過實際上我們也確實沒有對程式碼進行轉碼處理,如果小夥伴想要驗證它沒有轉碼,很簡單,就是在我們的路口檔案中新增“let arr = [1, 2];console.log([...arr]);”,然後在打包之後我們在打包後的檔案中搜索console.log找到我們上面console.log轉碼後的部分,然後就能看到了,小夥伴們可以去試試,這裡我們就不演示了。

接下來我們來了解下如何在打包的時候對程式碼進行相應的轉碼:

首先我們要安裝babel-loader包:npm install --save-dev babel-loader

然後在webpack.config.js中新增babel-loader規則:

   module: {
    rules: [
	...
      {
        test: /\.js$/,
        use: [
          'babel-loader'
        ]
      }
    ]
  }

 

這裡我們先打斷一下,我們先來看看官方是怎麼做的: 

上面是說還需要安裝babel-core包,該包是babel的核心依賴包,babel的核心api都在這裡面。

廢話就不多說了,我們繼續我們之前的操作,我們來看看沒有安裝babel-core能不能成功(很顯然是不能的,畢竟官網要下載babel-core包,也就說明babel-loader是建立在babel-core的基礎上來實現的),先來看看栗子:

先在我們的入口檔案,也就是index.js上,我們加入兩條語句:

let arr = [1, 2, 3];
console.log([...arr]);

然後我們打包生成bundle.js檔案,在該檔案中查詢console.log:


咦???語句已經被轉碼了,官網耍我們嗎???

別急,小夥伴們記得之前我們為了測試還安裝了babel-cli嗎,其實目前是這個在起作用,不信我們就把這個包給刪了。我們先把package.json中相應的模組刪除,然後刪除node_modules資料夾,然後再npm install一下。(個人不喜歡用uninstall來刪除包,因為之前有過幾次在通過這個命令刪除包之後,發現專案就出問題了)

完成上面的步驟,我們進行打包,發現出問題了。。。:


嗚嗚嗚。。。以後還是別太高看自己,官方不會騙人的,只能說你能力還不足而已。。。

然後我們再把babel-core進行安裝之後就和剛剛轉碼是一樣的,另外為了方便檢測我們仍然把babel-cli重新安裝,至於為什麼babel-core和babel-cli都可以,其中的原理小夥伴們可以自行去研究一下。

之前在網上看到別人寫的只安裝webpack和babel-core就能進行轉碼簡直就是扯淡,連最基本的babel-preset-XX都沒有,希望大家都嘗試去看看官方文件,以官方為準,或者自己多動手試試,不要被網上一些自以為是的人寫的文章所誤導。

四.plugins(外掛)

loader被用於轉換某些型別的模組,而外掛則可以用於執行範圍更廣的任務。外掛的範圍包括,從打包優化和壓縮,一直到重新定義環境中的變數。外掛介面功能極其強大,可以用來處理各種各樣的任務。

想要使用一個外掛,你只需要require()它,然後把它新增到plugins陣列中。多數外掛可以通過選項(option)自定義。你也可以在一個配置檔案中因為不同目的而多次使用同一個外掛,這時需要通過使用new操作符來建立它的一個例項。

這裡我們只簡單地講幾個外掛。

1.HtmlWebpackPlugin

這個外掛非常有用,它會生成一個html檔案,並將打包後的檔案新增到進去,也就省去了我們手動去建立去新增,特別是我們打包的檔名可能經常會更改,這時這個外掛就會非常有用。

現在我們來簡單演示一遍:

首先安裝該外掛:npm install --save-dev html-webpack-plugin

在webpack.config.js中進行配置,新增plugins屬性如下:

   plugins: [
    new HtmlWebpackPlugin({
      title: 'yaodebian',
      filename: 'index.html',
      template: './src/index.html',
      inject: true
    })
  ]
 

當然別忘了在檔案開頭對該外掛進行引入:const HtmlWebpackPlugin = require('html-webpack-plugin');

上面我進行了某些配置:title、filename、template、inject

title: 其實就是html檔案中title標籤的內容,上面的title配置其實會被template中指定的html檔案配置。在沒有配置template屬性時,自動生成的html檔案的title標籤欄位就是該title屬性的值。

filename: 指定生成的html檔案的位置,其相對於出口目錄而定;

template: 模板就是用來copy的,該屬性就是指定一個html檔案作為將會生成的html檔案的模板,相對於webpack配置檔案目錄而言,它的作用就是生成的檔案內容將會在template指定的檔案內容基礎上再將打包後的bundle.js檔案新增進去組合成一個新的html檔案。

  • inject: 它有四個值true、body、head、false
  • true 預設值,script標籤位於html檔案的 body 底部
  • body script標籤位於html檔案的 body 底部
  • head script標籤位於html檔案的 head中
  • false 不插入生成的js檔案,這個幾乎不會用到的

廢話不多說,舉個栗子:

像上面配置中,我是在src目錄下添加了一個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>webpack_all</title>
</head>
<body>
  <p class="hello">hello world</p>
  <img src="./Hydrangeas.jpg" alt="">
</body>
</html>

我們再來打包一下,結果可以看到dist目錄下生成了一個新的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>webpack_all</title>
</head>
<body>
  <p class="hello">hello world</p>
  <img src="./Hydrangeas.jpg" alt="">
<script type="text/javascript" src="bundle.js"></script></body>
</html>

看到了吧,就是將我們的模板新增上打包後的bundle.js檔案,這樣我們就不用自己建立html檔案並手動插入script。

不過有一個問題,這其實就是我們之前在講loader的時候講的那個問題,用file-loader引入的一張圖片,並會在出口目錄下會生成一張以圖片內容的MD5雜湊值命名的圖片。這個時候我們要在html檔案中通過img標籤來引用與呈現這張圖片時就需要手動更改標籤中src屬性中圖片的名字。我們想要讓它自動更改應該要怎麼實現呢?

自己在網上搜索了下,找到一個國人寫的loader: html-withimg-loader

首先,我們安裝該loader:npm install --save-dev html-withimg-loader,然後我們在webpack.config.js新增如下配置:

      {
        test: /\.html$/,
        loader: 'html-withimg-loader'
      }

最後在入口的js檔案中引入我們作為html-webpack-plugin模板的html檔案:

import './index.html'

這個時候我們進行打包,產生的index.html檔案如下:

dist/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>webpack_all</title>
</head>
<body>
  <p class="hello">hello world</p>
  <img src="bdf3bf1da3405725be763540d6601144.jpg" alt="">
<script type="text/javascript" src="bundle.js"></script></body>
</html>
 

我們看下dist目錄下的檔案結構:


看到了吧,這個時候img標籤的圖片名就和打包後的圖片是一樣的了。

不過,這個loader在平時應該不怎麼會去用,這裡只是針對之前遇到的問題使用這個loader。

2.clean-webpack-plugin

你可能已經注意到,由於過去的指南和程式碼示例遺留下來,導致我們/dist資料夾相當雜亂。webpack 會生成檔案,然後將這些檔案放置在 /dist 資料夾中,但是 webpack 無法追蹤到哪些檔案是實際在專案中用到的。

通常,在每次構建前清理 /dist 資料夾,是比較推薦的做法,因此只會生成用到的檔案。讓我們完成這個需求。

上面兩段是官方的原話,可能有些小夥伴不理解。通俗的講,假如我們dist目錄下有一個hello.js檔案,但是我們打包生成的檔案中沒有這個檔案,那打包之後這個檔案就是多餘的,我們根本不會用到,所以我們需要通過某些手段在打包之前將dist目錄清空。這裡,我們用到了clean-webpack-plugin。

安裝該外掛:npm install --save-dev clean-webpack-plugin

在webpack.config.js檔案中進行如下配置:

新增外掛宣告:const CleanWebpackPlugin = require('clean-webpack-plugin');

使用外掛:new CleanWebpackPlugin(['dist'])

上面初始化外掛配置時傳入的引數就是包含了要清空的目錄的陣列,詳細的配置可以去官網檢視。

為了驗證外掛的效果,我們先在dist目錄下建立一個hello.js的檔案:

最後我們再進行打包:npm run build

結果如下:

說明外掛已經生效了!!!

對於上面的應用,官方提出了一個叫做Manifest的概念:

這裡就不贅述,小菜我也還沒研究過,希望之後找個時間再另外單獨研究並總結一下。同樣也希望各位小夥伴或大大分享相關方面的知識或文章哦!!!

五.suorce map

當 webpack 打包原始碼時,可能會很難追蹤到錯誤和警告在原始碼中的原始位置。例如,如果將三個原始檔(a.js, b.js 和 c.js)打包到一個 bundle(bundle.js)中,而其中一個原始檔包含一個錯誤,那麼堆疊跟蹤就會簡單地指向到 bundle.js。這並通常沒有太多幫助,因為你可能需要準確地知道錯誤來自於哪個原始檔。

為了更容易地追蹤錯誤和警告,JavaScript 提供了 source map 功能,將編譯後的程式碼映射回原始原始碼。如果一個錯誤來自於 b.js,source map 就會明確的告訴你。

Source map有很多不同的選項可用,請務必仔細閱讀它們,以便可以根據需要進行配置。

上面是官方的原話,本人懶,直接拷貝過來了。。。

好了,現在來看栗子了,這裡使用的是官方的栗子(其他的看心情再搗鼓搗鼓。。。似不似很欠扁。。。哈哈):

首先,我們在webpack.config.js中新增devtool選項:

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

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  devtool: 'inline-source-map',
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader'
        ]
      },
      {
        test: /\.js$/,
        use: [
          'babel-loader'
        ],
        exclude: /node_modules/,
        include: path.resolve(__dirname, 'src')
      },
      {
        test: /\.html$/,
        loader: 'html-withimg-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'yaodebian',
      filename: 'index.html',
      template: './src/index.html',
      inject: true
    }),
    new CleanWebpackPlugin(['dist'])
  ]
}

然後先對入口檔案進行一些修改,新增如下:

hahaha();

因為hahaha()之前我們並沒有宣告過,肯定會報錯。我們先打包:npm run build

然後我們執行一下:


點進去看看:


我們看到,明明是23行出錯,但是它卻說21行報錯,是source map解析出錯了嗎,不禁產生這樣的想法。

我們在22行處新增consoe.log('yaodebian'):

import './index.html'

import './style.css';
import Icon from './Hydrangeas.jpg'
console.log('yaodebian');
let arr = [1, 2, 3];
console.log([...arr]);

function component() {
  let element = document.createElement('div');

   // 將影象新增到我們現有的 div。
   let myIcon = new Image();
   myIcon.src = Icon;

   element.appendChild(myIcon);

  return element;
}

document.body.appendChild(component());
consoe.log('yaodebian');
hahaha();
 

我們再次打包看看:



這次卻是正確的,很好的標記出了錯誤,至於為什麼之前的不準確目前不知道其原理,姑且先將它視作是souce map解析的問題吧,有知道的小夥伴請告知一下下。

之後看了幾篇文章,對於devtool這個配置選項,不同的屬性值會導致不同程度的souce map解析定位,以及會影響構建速度,不過並沒有怎麼看懂。這裡就不再贅述了,涉及的內容比較多,想之後再單獨抽時間總結一下。

接著上面的繼續。。。還有好多知識點。。。

六.開發工具(自動編譯程式碼)

每次要編譯程式碼時,手動執行npm run build特別麻煩。

webpack中有幾個不同的選項,可以幫助你在程式碼發生變化後自動編譯程式碼:

1.webpack's Watch Mode(觀察模式)

2.webpack-dev-server(提供一個伺服器)

3.webpack-dev-middleware(是一個容器(wrapper),它可以把 webpack 處理後的檔案傳遞給一個伺服器(server)。 )

webpack's Watch Mode(觀察模式)


上面是官方原話,沒有看懂就先來看一個栗子:

首先在package.json中新增一個npm script指令碼:

=================以下內容更新於2018.9.20======================
拖了將近一個月了,媽賣批的~~~

======================================================

"script": {
  ...,
  "watch": "webpack –watch",
  ...
}

現在我們就可以在命令列中執行npm run watch啟動觀察模式,然後開啟之前建立的index.html檔案,報錯:

這是因為之前為了檢測souce map而設定的錯誤,我們把它去掉就好。

然後我們在src中的入口檔案(index.js)檔案中新增一句“console.log('watch mode')”,儲存,會發現系統自動幫我們重新編譯了(說明我們的配置生效了),重新重新整理頁面則會看到如下:

控制檯上則多出了一句watch mode。

觀察模式雖然能夠達到自動編譯程式碼的效果,但是其有一個缺點,就是隻能幫我們自動重新編譯,但不會幫我們重新重新整理瀏覽器。如果想要在重新編譯程式碼的同時重新整理瀏覽器,則可以嘗試使用webpack-dev-server。

另外,在執行watch mode後,會看到webpack編譯程式碼卻不會退出命令列,這是因為script指令碼還在觀察檔案。

使用webpack-dev-server

webpack-dev-server提供一個簡單的web伺服器,並且能夠實時重新載入(living reloading)。

首先我們需要安裝相應的包:npm install –save-dev webpack-dev-server

然後在webpack.config.js中新增devServer選項:

module.exports = {
  ...,
  devServer: {
    contentBase: './dist'
  },
  ...
}

以上的配置使得webpack-dev-server將在localhost:8080下建立服務,將dist目錄下的檔案,作為可訪問檔案。

最後再在package.json檔案中新增script指令碼:

"script": {
  ...,
  "start": "webpack-dev-server --open",
  ...
}

現在,通過在命令列中執行npm start(注意這裡是直接用npm start,一般我們都是使用npm run start,webpack-dev-server比較特殊,我們還是使用通用的npm run start以免出錯),就會看到瀏覽器自動載入頁面。如果現在修改和儲存任意原始檔,web伺服器就會自動重新載入編譯後的程式碼。

使用webpack-dev-middleware

webpack-dev-middleware 是一個容器(wrapper),它可以把 webpack 處理後的檔案傳遞給一個伺服器(server)。 webpack-dev-server 在內部使用了它,同時,它也可以作為一個單獨的包來使用,以便進行更多自定義設定來實現更多的需求。

接下來是一個webpack-dev-middleware配合express server的示例。

首先安裝express和webpack-dev-middleware包:

npm install –save-dev express webpack-dev-middleware

接下來對webpack配置檔案做一些調整,以確保中介軟體(middleware)功能能夠正確啟用:

webpack.config.js:
output:{
  ...,
  publicPath: '/'
}

publicPath也會在伺服器指令碼用到,以確保檔案資源能夠在http://localhost:3000下正確訪問,

我們稍後再設定埠號。下一步就是設定我們自定義的 express 服務:

專案根目錄下我們新增一個server.js檔案(server.js):

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
 
const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);
 
// Tell express to use the webpack-dev-middleware and use the webpack.config.js
// configuration file as a base.
app.use(webpackDevMiddleware(compiler, {
  publicPath: config.output.publicPath
}));
 
// Serve the files on port 3000.
app.listen(3000, function () {
  console.log('Example app listening on port 3000!\n');
});

現在新增一個npm script:

"script": {
  ...,
  "server": "node server.js",
  ...
}

七.tree shaking

新增一個通用模組:

src/math.js:

export function square(x) {
 console.log('square');
  return x*x;
}
 
export function cube(x) {
  console.log('cube');
  return x*x*x;
}

接著,更新入口指令碼,並使用其中一個方法:

src/index.js:

...
import {cube} from './math.js';
 
console.log(cube(2));
...

現在我們執行我們的npm指令碼npm run build,並檢查我們輸出的bundle:

注意,我們沒有匯入squre,但是它仍然被包含在bundle中,這個是為什麼呢,為什麼還會保留?小夥伴們可能會說“這還用問嗎,現在根本就沒有進行任何配置,自然不會幫我們進行優化”,真的是這樣嗎,我們繼續看。

現在,我們在package.json檔案中新增一個配置"sideEffects": false (這個配置的意思是將入口檔案涉及到的檔案都標識為不含副作用,以此來告知webpack,它可以安全地刪除未用到的export匯出)

然後輸入命令npm run build進行打包操作,結果如下:

我擦,根本沒有上面卵用,和文件上是一樣的啊。~~~莫急莫急,我們接著來~~~

前方高能,膽小勿入~~~(以下黑色粗體字為本人自我臆想,切莫相信,切莫相信)

同樣的,沒有進行treeShaking,沒有引用的函式還是包含在bundle.js檔案中,按道理,如果它真的會優化,當我們設定"sideEffects": false,它便會進行優化打包,實際上它沒有,那麼真的沒有嗎?

其實上面的所有情況(包括不設定sideEffects配置的情況)webpack都會幫我們進行優化,操作treeShaking,至於為什麼,這裡要提一下:

還記得官方怎麼說的嗎?

Tree shaking必須要依賴於es6的import和export,針對export引用的檔案以及真正使用到的部分進行優化處理,而我們在這之前進行了babel轉碼的操作,對於轉碼後的es5語法我們就不會使用到tree shaking。

是的,上面的純屬本人扯淡,其實事實並非如此,其實在加了sideEffects時,webpack確實已經將我們的程式碼進行tree shaking,我們繼續來玩玩吧:

我們在index.js入口檔案中新增這樣幾句,如:

function test() {
  return 1234;
}
 
test();

咱打下包吧,然後搜尋一下1234看看,結果:

看到沒找不到,說明webpack已經啟動了tree shaking並檢測出上述程式碼是dead code(無用程式碼),故而將上述程式碼刪除。

那~~~,為哈子我們import時的程式碼中square函式仍然保留呢?這個嘛~~~這個確實跟es6語法有關:因為我們之前不是用了babel嗎,babel把es6轉化為es5的過程中,會使得轉化之後的程式碼產生副作用,故而上述math.js中的square會存在其中。

A:那~~~不是說sideEffects設為false會將程式碼標識為pure(純的)嗎,怎麼還會有呢???

B:好說好說,看來這位同學沒有理解其中的意思,該配置將程式碼標識為純,就說明程式碼沒有副作用,沒有副作用的話,我們就可以刪除那些看似無用的程式碼(根據webpack的解析規則),而不用擔心誤刪了某些程式碼。Babel將es6轉化為es5,而程式碼沒有刪除,說明轉化後的程式碼不在tree shaking的無用程式碼佇列中。

A:哦哦,貌似懂了~~~

所以呢,為了刪除上面沒有用到的square函式,我們需要去除掉babel的作用效果,即在webpack.config.js配置檔案中註釋掉babel的配置,如下:

接著我們重新打包檔案:

看到沒有,現在只剩下cube這個函數了。

好了,接下來我們來看看通過sideEffects來將某些檔案標識為有副作用吧,這樣一來,tree shaking就不會隨便將該檔案中“看似無用”的程式碼刪除,而將它們保留下來,就是它會將“可能有副作用”的程式碼保留下來(具體的解析原理暫時沒有了解過),那些確實無用的程式碼仍然會被刪除。

其實,就是說,當我們將程式碼標識為sideEffects:false時,可能會誤刪具有副作用的程式碼,下面我們就來舉一個栗子吧(目前sideEffects仍然為false):

我們在src目錄中建立一個menu.js檔案:

function Menu() {
}
 
Menu.prototype.show = function() {
}
 
Array.prototype.unique = function() {
    // 將 array 中的重複元素去除
}
 
export default Menu;

並在index.js中將其引入:

import Menu from './menu.js';

然後我們打包看看:

居然找不到之前的Array.prototype.unique = function() {...},心裡媽賣批“這句是我們要的,是有副作用的,你憑啥刪了”。。。

然後tree shaking小姐姐回了一句:“就憑你沒告訴我啊,我怎麼知道這句是有用的”

“好了好了,下次執行之前,我跟你講一句好了~~~”

現在我們將sideEffects設定為一個數組如:

看到了沒,出來了吧,所以如果我們不能保證我們的程式碼是純的(pure),也就是沒有副作用,那麼我們便要將相應的檔案標識為有副作用,以免某些程式碼會被誤刪。上面的只是找到的一個特例,我們平常也不會那樣用,我們會import其中的menu,並執行它,這樣Array.prototype將會被引用進來,也就不需要上面標識了。具體還有什麼比較切實際的栗子目前暫不可知,有知道的哥們或者小姐姐請留言分享一下。

好了,基本上tree shaking就講的差不多了,不過還有一個東西要講,就是webpack4(上面測試的版本是4.16.5,是預設支援的,前面幾個版本就不知道了)預設是帶有tree shaking功能的,也就是說我們不用配置sideEffects其實已經有tree shaking了,sideEffects只是起一個標識的作用。下面我們來測試一下吧:

將sideEffects配置去掉,然後打包一下:

我們發現沒有square,而只有cube,另外我們查一下之前新增的test函式是否存在,就是這個函式:

結果:

所以,看到了吧,tree shaking生效了。

另外,除了預設具有tree shaking之外,還預設標記所有程式碼都有副作用,為了驗證這個猜想,我們進行一下檢測:

我們先把上一次打包後的bundle.js儲存到我們的src目錄下;

然後在pakage.json檔案中配置sideEffects為:["./src/*.*"],這樣就相當於標識多有檔案有副作用,我們只要對比此次打包後的檔案是否和上一次打包的檔案相同,就知道我們的猜想是否是正確的:

仔細對比一下就會發現它們是相同的,也就驗證了我們的猜想。

這一節也講了很久了,最後提醒一下,千萬要將引入的.css等一些檔案標識為有副作用,不然css檔案的引入會被刪除,也就不會應用於html檔案中。

更新中。。。

相關推薦

基於webpack4.0手動搭建web專案更新

閒暇之餘做的總結,仍在更新中,有錯誤或不足的地方請大家指正與分享見解,謝謝。 一.準備工作 1.初始化專案目錄:npm init -y 2.建立以下的目錄結構: --dist --src ----index.js --package.json(初始化後生成)

Dockerfile 之 tomcat執行MyEclipse搭建Web專案Docker系列

本文章來自【知識林】 在之前的講解中主要講述的是如何使用已經存在的Docker映象,當然這些映象對我們的使用肯定有很大的幫助,但很多時候我們是需要執行我們自己所定製開發的應用程式,這些應用程式在Doc

快速建立一個基於Gradle構建的SpringBoot Web專案SpringBoot-01

快速建立一個基於Gradle構建的SpringBoot Web專案 在建立SpringBoot專案之前需要提前配置好電腦環境:JDK 、Gradle。 首先我們會通過 SpringBoot 官方提供的 Spring Initializr 這樣的一個專

基於Springboot2.0的Dubbo入門專案dubbo-spring-boot-starter

Dubbo是阿里巴巴公司開源的一個高效能優秀的服務框架,使得應用可通過高效能的 RPC 實現服務的輸出和輸入功能,可以和Spring框架無縫整合。最近半年來,Dubbo的得到了快速的維護,官方也推出了整合Springboot的jar包,及其貼合時代潮流,那麼現在就來講Spri

springBoot 搭建web專案前後端分離,附專案原始碼地址

springBoot 搭建web專案(前後端分離,附專案原始碼地址)    概述 該專案包含springBoot-example-ui 和 springBoot-example,分別為前端與後端,前後端分離,利用ajax互動。 springBoot-exam

web專案jsp+Java錯誤總結

一:當部署專案時出現Project facet Java version 1.8 is not supported.(jdk版本不一致) 解決方案:錯誤專案右鍵properties ——》 找到project Facets修改裡面的Java版本(適合的版本)

idea 執行web 專案連線資料庫

一共要匯入的包: 專案的建立: 在WEB-INF 資料夾下,建立classes  和  lib 資料夾 file----project structure--------Modules-------Path     將Output path   和 Te

docker部署web專案用tomcat

建立tomcat容器   -d指後臺執行   --name : 起別名    -p 對映的埠docker run tomcat --name mytomcat -p 8080:8080 -d進入容器內部  容器內部有一些設定檔案 只能通過這種方式進入 互動式的進入tomcat

手動搭建ssm框架適宜新手

在公司一直做分散式的專案,專案基本拿過來的時候架構師把整體框架,所用技術全都選擇好了,我們只要按照約束去寫程式碼就好了,沒事,我就想看自己是否還能搭建出ssm框架,說實話,還是耗費力氣的,不過總算弄出來了,適合顯新手。 我的jdk是1.7版本的 首先我們新建一個maven

IntelliJ IDEA 建立Web專案全教程

轉載:https://www.cnblogs.com/jxldjsn/p/8203859.html說明:IntelliJ IDEA 版本為14.JDK 版本為1.7tomcat 版本為apache-tomcat-7.0.70注:在建立過程中注意相關軟體版本位數的問題。32位,

如何用Maven建立web專案具體步驟

最後一步,我們要把當前的build path 指向 Maven Dependency, 直接點選add,選擇Java Build Path Entries 然後next 然後再點選finish完成 完成後如下圖: 至此一個基於maven的webapp就建立好了,並可以直接從e

無需付費,教你IDEA社群版開發Web專案SpringBoot\Tomcat

1、IDEA 版本介紹 最近有小夥伴私信我說 IDEA 破解怎麼總是失效?難道就沒有使用長一點的嗎... 咳咳,除了給我留言「啟用碼」外,或許社群版可能完全滿足你的需求。 相信有挺多小夥伴可能不清楚或者沒聽過社群版,其實 IDEA 有三個版本: Community:社群版,相當於 OpenJDK 的存在,

優雅的使用 PhpStorm 來開發 Laravel 專案翻譯

Laravel 是一個免費的開源的PHP框架,它建立在Symfony 元件之上、提供了使日常開發所用到的諸如認證、路由、sessions和快取管理的實現變得更加容易的框架。 在這個教程裡,我們將看到使用Phpstorm的Laravel外掛和Laravel IDE helper來充分利用Ph

《21個專案玩轉深度學習》在spyder3.6上跑2.7遇到的問題彙總更新...

在spyder3.6上跑2.7遇到的問題彙總------------------------------------------------------1、NameError: name 'xrange' is not defined在Python 3中,range()的實現

PHP OAuth2.0 Server 搭建,問題解決持續更新...

oauth2 server php http://oauth.net/2/ Step-By-Step Walkthrough Ref: http://bshaffer.github.io/oauth2-server-php-docs/cookbook/

Delphi - 手把手教你基於D7+Access常用管理系統架構的設計與實現 更新

前言   從事軟體開發工作好多年了,學的越深入越覺得自己無知,所以還是要對知識保持敬畏之心,活到老,學到老! 健身和程式碼一樣都不能少,身體是革命的本錢,特別是我們這種高危工種,所以小夥伴們運動起來!有沒有健身擼鐵,體脂現在是多少呀?明年(2019/03/22)徐州的馬拉松有沒有報名呀!?   扯的有點遠了,

vfd-cloud——一個適合練習上手的雲端儲存網盤springboot專案開發

# vfd-cloud [![](https://img.shields.io/badge/springboot-2.4.0-blue)](https://spring.io/projects/spring-boot)   [![](https://img.shields.io

redis 在Windows下的安裝及基本操作更新~~~

有用 redis 安裝 abc nbsp com inux eas pan 安裝目錄 Redis 安裝 Window 下安裝 下載地址:https://github.com/MSOpenTech/redis/releases。 Redis 支持 32 位和 64 位。這個需

《深入理解Java虛擬機:JVM高級屬性與最佳實踐》讀書筆記更新

pen 內存區域 深度 span 進化 ria 最短 描述 core 第一章:走進Java 概述 Java技術體系 Java發展史 Java虛擬機發展史 1996年 JDK1.0,出現Sun Classic VM HotSpot VM, 它是 Sun JDK 和 Open

python總結更新

註意 整數 元素 生成 更新 ron 外部 也會 mmu 1 python函數中的參數傳遞(註意可變和不可變傳遞) 可更改(mutable)與不可更改(immutable)對象 在 python 中,strings, tuples, 和 numbers 是不可更改的對象,而