使用 webpack 構建應用

天天天天天發表於2019-03-01

如何使用webpack

npm init -y
npm install webapck webpack-cli --save-dev
touch webpack.config.js
複製程式碼

webpack.config.js中下面新增內容

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
};
複製程式碼
  • entry:工程資源的入口,可以是單個檔案,也可以是多個檔案,通過每一個資源入口,webpack會一次去尋找它的依賴進行模組打包。我們可以把entry理解為整個依賴樹的根,每個入口都將對應一個最終生成的打包結果。
  • output:這是一個配置物件,通過它我們可以對最終打包的產物進行配置,這裡配置了兩個屬性,:
    • path:打包資源放置的路勁,必須為絕對路徑。
    • filename:打包結果的檔名。

定義好配置檔案後,用npx webpack或者./node_modules/.bin/webpack執行

使用loader

專案中需要引入一個css檔案,如果直接用webpack去執行就會報錯,需要再webpack中加入loader機制

module.exports = {
    ...
    module: {
		rules: [
			{
				// 用正則去匹配 .css 結尾的檔案,然後需要使用 loader 進行轉換
				test: /\.css$/,
				use: ['style-loader', 'css-loader']
			}
		]
	}
}
複製程式碼

編譯之前還需要安裝style-loadercss-loader

npm install --save-dev style-loader css-laoder
複製程式碼

注意:

  1. use屬性的值是一個使用loader名稱組成的陣列,loader執行的順序是從後往前的,由於loader執行有順序,故不能寫成這樣
use: ['css-loader', 'style-loader']
複製程式碼
  1. 每個loader都可以通過URL queryString的方式傳入引數,比如'css-loader?url'
  2. style-loader的原理:是將css的內容使用javascript的字串儲存起來,在網頁執行javascript時通過DOM操作,動態地向HTML head標籤裡插入HTML style標籤。
  3. 配置loader的方式也可以用Object來實現
use: ['style-loader', {
    loader: 'css-loader',
    options: {
        url: true
    }
}]
複製程式碼

使用plugin

loader的作用是被用於轉換某些型別的模組,而外掛則可以用於執行範圍更廣的任務,外掛的範圍包括,從打包優化和壓縮,一直到重新定義環節中的變數。

如果想要使用一個外掛,我們只需要require()它,然後把它新增到plugins陣列中。我們可以在一個配置檔案中因為不同的目的多次使用用一個外掛,因此我們可以使用new操作符來建立它的實列。

上面使用loadercss載入到js中去,現在使用extract-text-webpack-plugin外掛把bundle.js檔案裡的css提取到單獨的檔案中

// 提取 css 的外掛
const ExtractTextPlugin = require('extract-text-webpack-plugin')

module: {
    rules: [
        {
            // 用正則去匹配 .css 結尾的檔案,然後需要使用 loader 進行轉換
            test: /\.css$/,
            loaders: ExtractTextPlugin.extract({
                //轉換 .css需要使用的 loader
                use: ['css-loader']
            })
        }
    ]
},
plugins: [
    //從 js 檔案中提取出來的 .css 檔名稱
    new ExtractTextPlugin({
        filename: 'main.css'
    })
]
複製程式碼

編譯之前需要安裝extract-text-webpack-plugin

npm install --save-dev extract-text-webpack-plugin
複製程式碼

執行webpack時報錯需要這樣安裝

npm install extract-text-webpack-plugin@next
複製程式碼

DevServer

官方網站

安裝

npm install webpack-dev-server --save-dev
複製程式碼

然後進行簡單的配置

devServer:{
    port: 3000,
    publicPath: "/dist"
}
複製程式碼

然後用./node_modules/.bin/webpack-dev-server執行

資源壓縮

npm i uglifyJSPlugin-webpack-plugin --save-dev
複製程式碼

配置檔案

const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
plugins: [
    new UglifyJSPlugin({
        //預設是 false 需要手動開啟
        parallel: true
    })
]

或者

optimization: {
    minimizer: [new UglifyJsPlugin()],
},
複製程式碼

按需載入

在程式碼層面,webpack支援兩種方式進行非同步模組載入,一種是CommonJS形式的require.ensure,一種是ES6 Module形式的非同步import()

非同步載入的指令碼不允許使用document.write,所以將module.js的程式碼改成console.log

export const log = function(){
    console.log('module.js loaded.')
}
複製程式碼

編輯app.js,將module.js以非同步的形式載入進來

import('./module.js').then(module =>{
    module.log()
}).catch(error => "An error occurred while loading the module")
document.write('app.js loaded.')
複製程式碼

修改配置

module.exports = {
    mode: "production",
    entry: './app.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
        publicPath: "./dist"
    },
}
複製程式碼

這裡我們在output中新增了一個配置項publicPath,它是webpack中一個很重要有很容易引起迷惑的配置,當我們的工程中有按需載入以及圖片和檔案等外部資源時,就需要它來配置這些資源的路徑,否則頁面上就會報404,這裡我們將publicPath配置為相對於html的路徑,使按需載入的資源生產在dist目錄下,並且能正確地引用到它。

重新打包之後你會發現打包結果中多出一個1.mian.js,這裡面就是將來會被非同步載入進來的內容。重新整理頁面並檢視chromenetwork標籤,可以看到頁面會請求1.main.js。它並不來源於index.html中的引用,而是通過main.js在頁面插入了script標籤來將其引入的。

使用webpack的構建特性

2.0.0版本開始,webpack開始加入了更多的可以優化構建過程的特性。

tree-shaking

在關於模組的那一篇文章中我們提到過,ES6 Module的模組依賴解析是在程式碼靜態分析過程中進行的。換句話說,它可以在程式碼的編譯過程中得到依賴樹,而非執行時。利用這一點webpack提供tree-shaking功能,它可以幫助我們檢測工程中哪些模組有從未被引用到的程式碼,這些程式碼不可能被執行到,因此也稱為“死程式碼”。通過tree-shakingwebpack可以在打包過程中去掉這些死程式碼來減小最終的資源體積。

開啟tree-shaking特性很簡單,只要保證模組遵循ES6 Module的形式定義即可,這意味著之前所有我們的例子其實都是預設已經開啟了的。但是要注意如果在配置中使用了babel-preset-es2015或者babel-preset-env,則需要將其模組依賴解析的特性關掉,如:

presets: [
    [env, {module: false}]
] 
複製程式碼

這裡我們測試一下tree-shaking的功能,編輯module.js:

// module.js 
export const log = function() { 
    console.log('module.js loaded.'); 
} 

export const unusedFunc = function() { 
    console.log('not used'); 
} 
複製程式碼

開啟頁面檢視1.main.js的內容,應該可以發現unusedFunc的程式碼是不存在的,因為它沒有被別的模組使用,屬於死程式碼,在tree-shaking的過程中被優化掉了。

tree-shaking最終的效果依賴於實際工程的程式碼本身,在我對於實際工程的測試中,一般可以將最終的體積減小3%~5%。總體來看,我認為如果要使tree-shaking發揮真正的效果還要等幾年的時間,因為現在大多數的npm模組還是在使用CommonJS,因此享受不了這個特性帶來的優勢。

scope-hoisting

scope-hoisting(作用域提升)是由webpack3提供的特性。在大型的工程中模組引用的層級往往較深,這會產生比較長的引用鏈。scope-hoisting可以將這種縱深的引用鏈拍平,使得模組本身和其引用的其它模組作用域處於同級。這樣的話可以去掉一部分 webpack的附加程式碼,減小資源體積,同時可以提升程式碼的執行效率。

目前如果要開啟scope-hoisting,需要引入它的一個內部外掛:

module.exports = { 
    plugins: [ 
        new webpack.optimize.ModuleConcatenationPlugin() 
    ] 
}
複製程式碼

scope-hoisting生效後會在bundle.js中看到類似下面的內容,你會發現log 的定義和呼叫是在同一個作用域下了:

// CONCATENATED MODULE: ./module.js 
const log = function() { 
    console.log('module.js loaded.'); 
} 

// CONCATENATED MODULE: ./app.js 
log();
複製程式碼

相關文章