Webpack核心概念解析

banggan發表於2019-05-09

原文連結:banggan.github.io/2019/05/09/…

Webpack核心概念解析

終於忙完了論文,可以愉快的開始學習了,重拾起重學前端、webpack以及Vue的原始碼解讀作為入職前的複習吧。整個webpack系列將分成五個大的部分進行,以webpack4.0為文件進行解讀,從簡單的概念解讀到最後的實現。 整個知識點涉及範圍:

image

loader

使用loader來預處理檔案,把不同的靜態資源(模組的結尾不是js的模組)打包成js檔案

loader打包靜態資源

打包圖片

  • 安裝使用file-loader實現:npm install file-loader -D
  • 在webpack.config.js中新增loader的配置
module.exports = {
    //打包專案的入口檔案
    entry: './src/index.js',
    module:{
        rules:[{
            test:/\.(jpg|png|gif)$/,//打包以jpg、png、gif結尾的所有圖片檔案
            use:{
                loader:'file-loader',
                options:{//placeholder 佔位符 
                    name:'[name]_[hash].[ext]',//保持原圖片的名字+hash值和字尾,主要單引號
                    outputPath:'image/'//打包圖片的位置
                }
            }
        }]
    }
}
複製程式碼

打包圖片成base64格式

url-loader基本能實現file-loader的打包功能,適用於小圖片的打包

  • 好處:圖片打包成js檔案,不用載入圖片的地址,頁面快速顯示
  • 壞處:圖片過大導致js檔案過大

所以,當圖片的大小小於limit值時會把圖片打包成base64格式,大於limit值則按照file-loader打包成圖片檔案

  • 安裝使用url-loader實現:npm install url-loader -D
  • 在webpack.config.js中新增loader的配置
module.exports = {
    module:{
        rules:[{//打包以jpg、png、gif結尾的所有圖片檔案
            test:/\.(jpg|png|gif)$/,
            use:{
                loader:'url-loader',
                options:{//placeholder 佔位符 
                name:'[name]_[hash].[ext]',//保持原圖片的名字+hash值和字尾,主要單引號
                outputPath:'image/',//打包圖片的位置
                limit:2048
            }
        }]
    }
}
複製程式碼

打包樣式css檔案

需要使用css-loader、style-loader

  • css-loader:分析幾個css檔案的關係,合併css檔案
  • style-loader:將css-loader合併的css內容掛載在頁面的head部分

實現方式:

  • 安裝loader實現:npm install css-loader style-loader -D
  • 在webpack.config.js中新增loader的配置
module.exports = {
    module: {
        rules: [{//打包css檔案
            test:/\.css$/,
            use:['style-loader','css-loader']
        }]
    }
}
複製程式碼

打包樣式scss檔案

需要使用sass-loader、node-sass

  • 安裝loader實現:npm install sass-loader node-sass -D
  • 在webpack.config.js中新增loader的配置
module.exports = {
    module: {
        rules: [{
            test: /\.scss$/,
            use:['style-loader','css-loader','sass-loader'] 
        }]
    }
};

複製程式碼

在配置中,有三個loader,執行順序是從下到上,從右到左。在打包scss檔案時,首先執行sass-loader:對sass翻譯成css檔案,在掛載到css-loader,最後style-loader.

為樣式新增不同瀏覽器的字首

為了相容不同的瀏覽器,在寫樣式的時候需要加上適用不同瀏覽器的字首,如-o、-webkit、-moz等

-安裝loader實現:npm install postcss-loader autoprefixer -D -在根目錄建立postcss.config.js

moudle.exports ={
	plugins:[
        require('autoprefixer')
	]
}
複製程式碼
  • 在webpack.config.js中新增loader的配置
module.exports = {
    module: {
        rules: [{
            test:/\.scss$/,
            use:[
            'style-loader',
            'css-loader',
            'sass-loader',
            'postcss-loader']
        }]
    }
}
複製程式碼

css-loader新增不同的配置

css模組化打包
  • 場景:在檔案引入的scss不僅影響當前的檔案,還影響當前檔案引入的其他js檔案,造成樣式衝突
  • 實現:css只在當前模組類有效,在配置中新增modules:true開啟css的模組化打包,在引入的時候注意區分
scss檔案的巢狀引用
  • 場景:scss檔案通過import引入其他scss檔案,導致打包的時候引入的scss檔案打包錯誤

  • 實現:importLoader:2

  • 在webpack.config.js中新增loader的配置

module:{
        rules:[{//打包scss檔案
            test:/\.scss$/,
            use:[
            'style-loader',
            {
                loader:'css-loader',
                options:{
                    importLoaders:2,//index.scss中通過import引入其他的scss檔案,引入的scss檔案在打包的時候也將依次經過所有的loader
                    modules:true 
                }
            },
            'sass-loader',
            'postcss-loader']
        }]
    }
複製程式碼

打包字型圖示檔案

阿里巴巴向量圖示庫中,把需要的字型圖示下載到本地,解壓。將iconfont.eot iconfont.svg iconfont.ttf iconfont.woff 四種圖片檔案放入到專案中,在src中新建一個放字型圖示的資料夾font。將iconfont.css檔案拷貝到專案中,修改對應字型的引用路徑。

  • 需要安裝 file-loader:npm i file-loader -D
  • 在webpack.config.js中新增loader的配置
module.exports = {
    ...
    module: {
        rules: [{
            test: /\.(eot|ttf|svg|woff)$/,
            use:{
                loader:'file-loader'
            }
        },
            ]
        }]
    }
}
複製程式碼

打包資料檔案

如遇到json、scv、xml檔案需要打包時,使用csv-loader 和 xml-loader實現。

  • 安裝:npm install csv-loader xml-loader -D
  • 在webpack.config.js中新增loader的配置
  module.exports = {
    module: {
      rules: [{
         test: /\.(csv|tsv)$/,
         use: [
           'csv-loader'
         ]
       },
       {
         test: /\.xml$/,
         use: [
           'xml-loader'
         ]
       }]
    }
  }
複製程式碼

plugins

loaders可以將各個型別的靜態資源打包成webpack能處理的模組,而plugins有更強大的功能。它可以從打包優化和壓縮,一直到重新定義環境中的變數。

plugin可以在webpack執行到某一個時刻,自動完成一些事情。

自動生成html檔案,並引入打包生成的js檔案到生成的html檔案中

  • 安裝使用HtmlWebpackPlugin實現:npm install html-webpack-plugin -D
  • 在webpack.config.js中新增loader的配置

在src中建立一個html的模板,在HtmlWebpackPlugin的配置中引入該模板,打包後生成和模板類似的html檔案並引入打包的js檔案。

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

module.exports = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index_bundle.js'
  },
    plugins: [new HtmlWebpackPlugin({
        template: 'src/index.html' 
    })]
}
複製程式碼

自動清除上一次打包的dist檔案

先刪除上一次打包的dist檔案,再執行打包

  • 安裝使用CleanWebpackPlugin 實現:npm install clean-webpack-plugin -D
  • 在webpack.config.js中新增loader的配置
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const path = require('path');

module.exports = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index_bundle.js'
  },
    plugins: [
        new HtmlWebpackPlugin({
        template: 'src/index.html' 
    }),
        new CleanWebpackPlugin(['dist']), // 在打包之前,可以刪除dist資料夾下的所有內容
    ]
}
複製程式碼

Entry與Output的基礎配置

  • 需求:打包多個入口檔案,對應的在出口的配置中注意命名filename避免出口檔名字衝突----使用佔位符)來確保每個檔案具有唯一的名稱
  • 需求:打包後的檔案,作為後端的介面檔案,靜態資源需要上傳到cdn,在output中進行配置,提前加入cdn的地址。在編譯時,如果不知道publicPath的地址,可以留空,在入口起點檔案執行時動態設定。__webpack_public_path__ = myRuntimePublicPath
module.exports = {
	mode: 'development',
	entry: {
		main: './src/index.js',
		sub: './src/index.js'
	},
	plugins: [new HtmlWebpackPlugin({
		template: 'src/index.html'
	}), new CleanWebpackPlugin(['dist'])],
	output: {
		publicPath: 'http://cdn.com.cn', //加入cdn地址
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}
複製程式碼

SourceMap的配置

SourceMap是一個對映關係,打包檔案和原始檔的對映關係,用於開發者的除錯。

在devtool中進行設定:devtool: 'source-map'打包速度會降低,在dist裡面會有map對映檔案

常用設定devtool說明:

  • none:在開發者模式下,預設開啟sourcemap,將其關閉
  • inline字首:不單獨生成map檔案,把對應的map檔案以base64的形式直接打包到js檔案。
  • cheap字首:sourcemap和打包後的js同行顯示,並沒有對映到列忽略源自 loader 的 source。 map,並且僅顯示轉譯後的程式碼,所以打包速度相對來說較快。程式碼出錯提示不用精確顯示第幾行的第幾個字元出錯,只顯示第幾行出錯,會提高一些效能,
  • moudle字首:不僅對映業務程式碼,還會包括loader、第三方模組的錯誤。
  • eval字首:打包速度最快。

development環境推薦使用: devtool: 'cheap-module-eval-source-map'

production環境推薦使用: devtool: 'cheap-module-source-map'

使用WebpackDevServer提升開發效率

場景:每次在src裡編寫完程式碼都需要手動重新執行 npm run bundle,如何自動解決?

-安裝:npm install webpack-dev-server –D

  • devServer引數說明
  1. contentBase :配置開發服務執行時的檔案根目錄
  2. open :自動開啟瀏覽器
  3. host:開發伺服器監聽的主機地址
  4. compress :開發伺服器是否啟動gzip等壓縮
  5. port:開發伺服器監聽的埠
  • 在 webpack.config.js 中,加 devServer
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080
		proxy:{//配置跨域,訪問的域名會被代理到本地的3000埠
      	'/api': 'http://localhost:3000'
    	}
	}
}
複製程式碼
  • 在 package.json 中配置
{
  "name": "banggan",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "bundle": "webpack",
    "watch": "webpack --watch",
    "start": "webpack-dev-server",
  },
}
複製程式碼

如何實現自己寫一個類似webpackdevserver的工具

  • 在package.json 中配置 建立一個新的指令,npm run server執行自己寫的類似webpackdevserver的工具
{
  "name": "banggan",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "bundle": "webpack",
    "watch": "webpack --watch",
    "start": "webpack-dev-server",
	"server" : "node server.js" 
  },
}
複製程式碼
  • 安裝express:npm install express webpack-dev-middleware -D
  • 在根目錄建立一個server.js
  • 在node中直接使用webpack:webpack(config)
const express = require('express'); //引入express
const webpack = require('webpack');//引入webpack
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');//引入配置檔案

const complier = webpack(config); //編譯一次嗎,打包一次程式碼
const app = express();//建立express例項

app.use(webpackDevMiddleware(complier, {}));

app.listen(3000, () => {//監聽3000埠
	console.log('server is running');
});
複製程式碼

Hot Module Replacement熱模組更新

  • 場景:在程式執行中。替換、新增、替換某個模組,不需要重新載入整個頁面,實現互動時更新。

  • 在 webpack.config.js 中,新增配置

module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,//開啟熱更新功能
		hotOnly: true//如果html功能沒有實現,也不讓瀏覽器重新整理
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist']),
		new webpack.HotModuleReplacementPlugin()//使用熱模組外掛
	],
}
複製程式碼
  • 在main.js檔案中,使用 HotModuleReplacementPlugin 啟用模組的熱替換功能。介面暴露在moudle.hot屬性下面
//如果模組啟用了HMR,就可以用 module.hot.accept(),監聽模組的更新。
if (module.hot) {
  module.hot.accept('./library.js', function() {
    // 使用更新過的 library 模組執行某些操作...
  })
}

複製程式碼
//拒絕給定依賴模組的更新,使用 'decline' 方法強制更新失敗。
module.hot.decline(
  dependencies // 可以是一個字串或字串陣列
)
複製程式碼

注意:引入css檔案時,用框架Vue,React 時,不需要寫 module.hot.accept(),因為在使用css-loader,vue-loader,babel-preset時,均配置好了HMR,不需要自己重新寫 如果檔案沒有內建HMR,需要自己手動寫監聽模組更新程式碼

結合Bable處理ES6語法

  • 場景:程式碼中含有ES6/ES7的程式碼,為了讓低版本的瀏覽器相容程式碼,需要使用Bable進行轉換
  • 安裝:Bable官網
//preset-env語法轉換
npm install babel-loader @babel/core @babel/preset-env -D
//相容低版本瀏覽器的語法,函式的補充
npm install --save @babel/polyfill

複製程式碼
  • 在 webpack.config.js 中,新增配置
module: {
  rules: [
    {
        test: /\.js$/,
     	exclude: /node_modules/, //排除在外:在node_modules中的js,babel-loader不生效
     	loader: "babel-loader" ,
        options:{
            "presets": [["@babel/preset-env",{
                targets: {
                    edge: "17",
                    firefox: "60",
                    chrome: "67",
                    safari: "11.1",
                  },//執行在大於**版本的瀏覽器上,,已經支援es6的高瀏覽器不需要轉換為es5
                useBuiltIns:'usage' //按需新增polyfill,把業務程式碼中的新語法新函式都轉成低版本瀏覽器相容的
            }]]
        }
    }
  ]
}

複製程式碼
  • 在src目錄下的index.js中頂部位置匯入import "@babel/polyfill";

注意如果不是打包業務程式碼,而是寫的類庫、或者z元件庫的時候不能使用@babel/polyfill實現,因為會導致宣告的變數變成全域性變數,汙染全域性環境。使用plugin-transform-runtime實現

  • 安裝外掛
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
npm install --save @babel/runtime-corejs2

複製程式碼
  • 在 webpack.config.js 中,新增配置
module: {
  rules: [
    {
        test: /\.js$/,
     	exclude: /node_modules/,
     	loader: "babel-loader" ,
        options:{
            "plugins": [["@babel/plugin-transform-runtime",{
                "corejs": 2,
                "helpers": true,
                "regenerator": true,
                "useESModules": false
            }]]
        }
    }
  ]
}

複製程式碼

由於babel配置的內容較多,官網推薦在根目錄下建立屬於babel的配置檔案.babelrc檔案

由於babel需要配置的內容非常多,我們需要在專案根目錄下建立一個 .babelrc 檔案。 就不需要在 webpack.config.js 中寫 babel 的配置了。 在 .babelrc 中:

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

總結

  • 在webpack.config.js中
module.exports = {
	mode: 'development', //開發環境進行打包,打包的程式碼不會壓縮
	devtool: 'cheap-module-eval-source-map',//不帶列資訊,只對業務程式碼進行sourcemap的生成 
	entry: {//配置入口檔案
		main: './src/index.js'
	},
	devServer: {//配置webpack,開發環境的除錯
		contentBase: './dist',//啟動伺服器的目錄
		open: true,//開啟新的埠號8080
		port: 8080,
		hot: true,//開啟熱替換功能
		hotOnly: true
	},
	module: {//對不同的檔案進行打包規則
		rules: [{ 
			test: /\.js$/, //對js檔案的babel-loader打包,其配置在.babelrc檔案中
			exclude: /node_modules/, //排除在外:在node_modules中的js,babel-loader不生效
			loader: 'babel-loader',
		}, {
			test: /\.(jpg|png|gif)$/,//對圖片進行打包
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240//小於10240以base64的形式進行打包
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,//字型檔案的打包
			use: {
				loader: 'file-loader'
			} 
		}, {
			test: /\.scss$/,//scss檔案的打包,先用postcss-loader,在用sass-loader進行解析,最後css-loader進行掛載
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,//css檔案的打包,沒有sass-loader的解析
			use: [
				'style-loader',
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist']),//自動清空上一次打包
		new webpack.HotModuleReplacementPlugin()//熱替換外掛
	],
	output: {//出口檔案
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}
複製程式碼

相關文章