前端繁榮發展,工程化已經成爲高級前端工程師的必不可少的條件之一,打包構建的發展從grunt
,fis
,glup
到rollup
,webpack
,Parcel
,技術手段變幻無窮,javascript
但其實不論任何一項技術或工具,都有五個階段,css
這五個階段越日後是越艱難,可是你越是日後深刻就越能透過表象看清它的本質,以在這快速變化的技術手段中站穩,以不變應萬變html
webpack如今是前端打包構建最流行的工具,那麼咱們就來好好了解一下它(webpack ^4.42.1)前端
首先梳理下本文要講到的內容vue
核心概念java
其餘經常使用配置node
優化手段react
配置總結webpack
下面逐個介紹css3
定義打包的入口
使用示例
// 簡寫
module.exports = {
entry: './src/index.js',
}
// 多入口
module.exports = {
entry: {
index: './src/index.js',
list: './src/index.js',
},
}
複製代碼
編譯後文件輸出到磁盤的相關配置
// 簡寫
module.exports = {
output: {
filename: '[name]_[chunkhash:8].js' //單個文件名可直接指定,多入口利用佔位符保證文件名統一
path: path.join(__dirname, '../dist') // 寫入文件磁盤路徑
publicPath: 'http://cdn.example.com/assets/'
//資源使用 CDN ,給全部文件引入模版文件時加上路徑前綴
},
}
複製代碼
佔位符
webpack 原生只支持js 和json,利用loader,對不一樣文件類型支持,轉換成有效的模塊 簡單示例
module.exports = {
module: {
rules:[
{
test: /\/.txt$/, // 指定匹配規則
use: 'babel-loader' // 指定使用的loader名稱
}
]
}
}
複製代碼
下面介紹幾種文件類型的處理以及經常使用的loader
babel-loader
:js默認是不支持es6 和jsx語法的,.babelrc
文件: 設置具體支持的屬性方法{
test: /\.(j|t)sx?$/,
use: 'babel-loader',
exclude: /node_modules/
},
複製代碼
style-loader
:將樣式經過style
標籤插入模版文件的head當中css-loader
: 用於加載.css文件 而且轉換成commonjs 對象{
test: /.css$/,
use: [
'style-loader',
'css-loader',
],
},
複製代碼
less-loader
:less轉換成css,{
test: /.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'less-loader',
options: { // 可配置屬性,修改變量的值,通常利用來修改UI庫的主題樣式,如下是antd主題樣式配置
modifyVars: {
'@primary-color': '#ec7259',
},
javascriptEnabled: true,
},
},
],
},
複製代碼
file-loader
:解析圖片, 字體等url-loader
:也可處理圖片和字體,,並可設置較小資源自動base64{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: 'static/img/[name].[hash:8].[ext]',// [ext] 文件的後綴名
},
},
],
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 8192,
name: 'static/fonts/[name].[hash:8].[ext]',
},
},
複製代碼
px2rem-loader
: 把px轉換成rem,配合lib-flexible使用{
loader: 'px2rem-loader',
options: {
remUnit: 75, // 1rem=多少像素
remPrecision: 8, // rem的小數點後位數
}
}
複製代碼
postcss-loader
:用於瀏覽器適配,某些css3屬性瀏覽器不支持須要加前綴,它會自動針對不一樣瀏覽器加不一樣的屬性前綴{
loader: 'postcss-loader',
options: {
plugins: () => [autoprefixer()],
},
},
複製代碼
plugins
屬性傳入new
實例 簡單示例const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugin: {
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
}
}
複製代碼
下面介紹幾種經常使用的plugin
new HtmlWebpackPlugin({
filename: '../dist/template/index.html', // 指定生成的模版文件名及路徑
template: path.join(__dirname, '../src/template/index.html'), // 指定要使用的模版文件
inject: true, // 指定的chunk會自動注入html文件中
chunks: ['index'], //指定生成的html要使用的chunk
minify: { // 代碼的最小化輸出
collapseWhitespace: true, // 刪除空格,可是不會刪除SCRIPT、style和textarea中的空格
preserveLineBreaks: false, // 是否保留換行符
minifyCSS: true, // css壓縮
minifyJS: true, // js壓縮
removeComments: true, // 刪除註釋,可是會保留script和style中的註釋
},
}),
複製代碼
rimraf dist
new CleanWebpackPlugin(),
複製代碼
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css'
}),
複製代碼
new OptimizeCssAsssetePlugin({
assetNameRegExp: /\.css$/g, //文件匹配
cssProcessor: require('cssnano') // cssnano 壓縮和優化的css插件
}),
複製代碼
舉例:
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
plugins: [
new HtmlWebpackExternalsPlugin({
externals:[
{
module: 'react',
entry: 'https://cdn.cn/16.8.0/react.min.js',
global: 'React',
},
{
module: 'react-dom',
entry: 'https://cdn.cn/16.8.0/react-dom.min.js',
global: 'ReactDOM',
}
]
}),
複製代碼
development
: 開發模式,production
: 生產模式,none
: 無webpack會針對不一樣環境直接作一些優化工做,例如production模式下會進行,tree-shaking
和scope-hosting
下面優化會詳細介紹
遠古時期,咱們作前端開發時,寫一個html文件,在瀏覽器打開它查看效果,修改時,需手動更新,
後來咱們使用熱更新,webpack低版本,不提供熱更新的支持,咱們使用插件http-proxy-middleware
和webpack-hot-middleware
,實現熱更新,配置比較麻煩
最後webpack把熱更新集成在內部,就成了devServer
簡單配置以下
devServer: {
historyApiFallback: true, // 單頁面程序 刷新瀏覽器會出現404,緣由是它經過這個路徑(好比: /search/list)來訪問後臺,因此會出現404,而把historyApiFallback設置爲true那麼全部的路徑都執行index.html
host: '127.0.0.1', // 域名
open: true, //支持自動打開瀏覽器
hot: true, // 模塊熱替換,在前端代碼變更的時候無需整個刷新頁面,只把變化的部分替換掉
inline: false, // inline選項會爲入口頁面添加「熱加載」功能,即代碼改變後從新加載頁面
port: 8080, // 端口
proxy: proxyConfig._proxy, // 代理後端服務,舉例:可本地調試測試接口
before(app) { // 其餘中間件以前, 提供執行自定義中間件
apiMocker(app, path.resolve('./mocks/mock.js'), // 舉例:可用來作mock數據
proxyConfig._proxy);
},
},
複製代碼
首先來看下簡單的流程示意圖
webpack compile
將JS編譯成bundle.jsHMR server
將熱更新文件輸出給HMR Runtime,HMR -> HotModuleReplacement(熱模塊替換)Bundle server
提供文件在瀏覽器的訪問HMR Runtime
注入瀏覽器,更新文件的變化,使瀏覽器 和 服務器創建一個連接(websocket)bundle.js
構建輸出的文件啓動階段
1 -> 2 -> 3
初始代碼通過webpack compiler編譯進行打包
編譯好的文件傳輸給bundle server, 它就至關於一個服務器,它使文件以server的方式讓瀏覽器訪問
files
-> webpack Compiler
-> Bundle Sever
-> bundle.js
更新階段
1 -> 4 -> 5 -> 6
file文件發生變化,通過webpack compiler編譯
編譯好的文件傳輸給HMR server,通知瀏覽器端的HMR Runtime(一般以JSON形式傳輸)
HMR Runtime 更新代碼,實現無刷新改變頁面內容
files
-> webpack Compiler
-> HMR server
-> HMR Runtime
-> code
介紹幾個經常使用的屬性用法
alias
建立 import 或 require 的別名,來確保模塊引入變得更簡單extensions
自動解析肯定的擴展mainFileds
當從 npm 包中導入模塊時,決定在 package.json 中使用哪一個字段導入模塊moudles
告訴 webpack 解析模塊時應該搜索的目錄舉例:
// webpack 配置文件
resolve: {
alias: {
Util: path.resolve(__dirname, 'src/util/'),
},
mainFileds: ['main'],
extensions: ['.js', '.jsx', '.json'],
moudles: [path.resolve(__dirname, 'node_modules')]
},
//業務文件 component.js
import Utility from '../../util/utility.js';
// 簡化寫法(不用寫文件路徑前綴,也不用寫引用文件的擴展名)
import Utility from 'Util/utility';
複製代碼
splitChunks
,代替以前的CommonsChunkPlugin,公共資源分離vendors
chunks屬性特別說明
splitChunks
公共文件分離commons
runtimeChunk
舉例:
optimization: {
runtimeChunk: {
name: 'manifest',
},
splitChunks: {
minSize: 50000 // 分離的包的體積大小
cacheGroups: {
vendors: {
test: /(react|react-dom)/, //正則匹配要分離的文件
name: 'vendors',
chunks: 'all', // 肯定對何種引入方式的文件進行分離
minChunks: 1, // 最小使用的次數
priority: 10, // 多個緩存組時,須要有優先級排列,優先使用哪一個進行分離
},
commons: { // 分離公共文件
name: 'commons',
chunks: 'all',
minChunks: 2,
priority: 5,
},
},
},
},
複製代碼
關鍵字定義
eval
模塊都使用 eval() 包裹執行,而且都有 //@ sourceURL(指向的是原文件index.js,調試的時候,根據sourceUrl找到的index.js文件的)source map
產生.map文件(這個map文件會和原始文件作一個映射,調試的時候,就是經過這個.map文件去定位原來的代碼位置的 )cheap
不包含列的信息,(假如代碼運行出現了錯誤,控制檯報出了,error,咱們點擊定位到具體源碼的時候,就只能定位到行,而不能定位到具體的列)inline
.map文件做爲dataUrl嵌入到打包文件,而不單獨生成幾種關鍵字進行組合就造成了具體的用法
不一樣用法對構建速度是有影響的,基本狀況你越清晰容易的看到原始的代碼,構建速度就越慢,調試的方便性和構建速度上你們能夠本身權衡一下
共有13種用法,詳細的請看官方文檔
舉例
// 開發環境
devtool: 'cheap-module-eval-source-map' // 原始源代碼(僅限行)
// 生產環境,通常不進行設置
複製代碼
構建統計信息
使用舉例
// 構建完成後會生成json文件,顯示構建的一些信息,時間,各模塊的體積等
scripts: {
'build: stats': 'webpack --config build/webpack.prod.config.js --json > stats.json',
}
複製代碼
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasureWebpackPlugin();
module.exports = smp.wrap(merge(webpackConfigBase, webpackConfigProd));
複製代碼
圖中咱們看到了每一個插件和loader的耗時狀況, 若是耗時較長,會以紅字提示,咱們就能夠具體分析那個地方爲何時間長,能夠用別的插件替換之類的去作構建速度優化
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
plugins: [
new BundleAnalyzerPlugin({
analyzerPort: 8919 //打包構建後體積分析圖展現的窗口
}),
],
複製代碼
運行打包命令,體積分析示意圖會自動打開在8919窗口
圖中咱們能夠看到moment插件佔用的空間很大,咱們能夠對它進行優化
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
// compoent
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
複製代碼
Dead Code(什麼是死碼呢?)
ES6 module 特色:
ES6模塊依賴關係是肯定的,和運行時的狀態無關,能夠進行可靠的靜態分析,這就是tree-shaking的基礎,讓死碼清除成爲了可能
靜態分析就是不執行代碼,僅僅從字面的意思上對代碼進行分析,ES6以前的模塊化,好比咱們能夠動態require一個模塊,只有執行後才知道引用的什麼模塊,這個就不能經過靜態分析去作優化,特別說明import()
動態引入也是不支持的
引用的文件tools.js
export default 'Hello World';
複製代碼
入口文件index.js
import str from './tools';
console.log(str);
複製代碼
未開啓scope-hoisting
編譯後文件
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
console.log(_tools__WEBPACK_IMPORTED_MODULE_0__["default"]);
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ('Hello World');
/***/ })
複製代碼
能夠看到
開啓scope-hoisting
編譯後文件
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ var tools = ('Hello World');
console.log(tools);
/***/ })
複製代碼
能夠看到
happypack
和thread loader
給咱們提供了方案因爲happypack做者再也不維護此項目,同時二者原理大體一致,咱們就主要介紹
thread loader
rules: [
{
test: /.js$/,
use: [
{
loader: 'thread-loader',
options: {
workers: 3, // 產生的 worker 的數量,默認是 cpu 的核心數
}
}
]
},
]
複製代碼
注意:thread loader 只有在項目龐大複雜的時候才能顯著的凸顯效果,若是是中小型項目沒有必要使用
日誌上報
plugins: [
// 主動捕獲構建錯誤
function () {
this.hooks.done.tap('done', (stats) => {
if (stats.compilation.errors
&& process.argv.indexOf('--watch' == -1)) {
console.log('error', stats.compilation.errors);
// 能夠作一個構建系統的日誌,在此處上報錯誤緣由
process.exit(12); // 自定義錯誤code碼
}
});
},
],
複製代碼
terser-webpack-plugin
開啓 paralleluglify-webpack-plugin
也支持並行壓縮,但不支持es6,不作具體介紹,有興趣的同窗自行查詢optimization: {
minimizer: [
new TerserPluginWebpack({
parallel: 4, // 開啓 不主動指定的話,默認數值是當前電腦cpu數量的2倍減1
})
],
}
複製代碼
dll-plugin
進行分包,dllreferenceplugin
對mainfest.json(對分離包的描述)引用,將預編譯的模塊加載進來add-asset-html-webpack-plugin
把生成文件插入模版舉例
// package.json
"scripts": {
"dll": "webpack --config build/webpack.dll.js" // 打包前只需執行一次,只要基礎包不變則不須要執行
},
// webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: { // 指定要分離的包
library: [ // 基礎包
'react',
'react-dom',
],
},
output: {
filename: "[name]_[hash].dll.js",
path: path.resolve(__dirname, './library'),
library: "[name]_[hash]", //包名稱 注意此名需和下面的DllPlugin,name名一致
},
plugins: [
new webpack.DllPlugin({
name: "[name]_[hash]",
path: path.resolve(__dirname, './library/[name].json')
})
]
}
// webpack.prod.js
plugins: [
...
// 將給定的 JS文件添加到 webpack 配置的文件中,並插入到模版文件中
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, '../build/library/*.dll.js'),
}),
//
new webpack.DllReferencePlugin({
manifest: require('../build/library/library.json')
}),
]
複製代碼
hard-source-webpack-plugin
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
plugins: [
...
new HardSourceWebpackPlugin()
]
]}
複製代碼
總結
vue-cli
和create-react-app
中並無使用dllhard-source-webpack-plugin
替換dll
舉例
const PATHS = {
src: path.join(__dirname, '../src')
}
plugin: [
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }), // 注意是絕對路徑匹配
}),
]
複製代碼
{
test: /.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name:'[name]_[hash:8].[ext]'
}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 75
}
}
},
],
},
複製代碼
https://polyfill.io/v3/polyfill.min.js
https://polyfill.alicdn.com/polyfill.min.js?features=Promise
咱們以實際目標爲導向收集整理一下經常使用webpack配置要作什麼事情
小結
雖說了不少關於webpack的配置和優化,但咱們仍是要根據項目的複雜程度,公司&項目的具體狀況,處理遺留代碼的難度,來選擇性的處理, 有時若是強行使用反而得不償失
固然最後要記住一切的技術都只是工具,都要以賦能業務,價值產出爲目標,打包工具的目標就是
原本想直接一篇文章直接整理完成的,內容較多,分篇記錄,未完待續。。。