詳談webpack4

前言

下載啥的就很少說了,就看看咱們項目中常常用到的一些配置。碼字不易,喜歡的話點個💗哦 ~~~css

webpack

webpack只是一個打包模塊的機制,只是把依賴的模塊轉化成能夠表明這些包的靜態文件。webpack就是識別你的 入口文件。識別你的模塊依賴,來打包你的代碼。至於你的代碼使用的是commonjs仍是amd或者es6的import。webpack都會對其進行分析。來獲取代碼的依賴。webpack作的就是分析代碼。轉換代碼,編譯代碼,輸出代碼。webpack自己是一個node的模塊,因此webpack.config.js是以commonjs形式書寫的(node中的模塊化是commonjs規範的) webpack中每一個模塊有一個惟一的id,是從0開始遞增的。整個打包後的bundle.js是一個匿名函數自執行。參數則爲一個數組。數組的每一項都爲個function。function的內容則爲每一個模塊的內容,並按照require的順序排列。html

clean-webpack-plugin

清空打包文件。new CleanWebpackPlugin(), 能夠不傳參,配置參考這裏vue

source map

sourcemap就是一個信息文件,裏面儲存着位置信息。目的是爲了解決開發代碼與實際運行代碼不一致時幫助咱們debug到原始開發代碼的技術。node

  • 配置 devtool 屬性 ( 最佳實踐 )
  • development: cheap-module-eval-source-map
  • production: cheap-module-source-map
  • 五個關鍵字任意組合: eval,source-map,cheap,module,inline
  1. eval: 包裹模塊,在模塊尾添加模塊來源 //#sourceURL,經過 sourceURL 找到原始代碼位置,不單獨產生.map 文件
  2. source-map: 產生.map文件,包含原始代碼和運行代碼的映射關係
  3. 參考文獻

webpack-dev-server

npm i webpack-dev-server -Dreact

  • 注意:打包的文件不會放到dist目錄中了,而是放在咱們的內存中,從而提高了打包速度。
devServer: {
    // open: true, // 打開瀏覽器
    // port: 8080, // default
    hot: true, // HMR,不刷新頁面就能應用你改過的css樣式
    hotOnly: true, // 若是HMR沒生效,也不刷新頁面
    contentBase: './dist', // 告訴服務器從哪裏提供內容。只有在您但願提供靜態文件時才須要這樣作。
    proxy: {
      '/api': 'http://localhost:3000' // 若是咱們訪問localhost:8000/api ,則轉發請求到localhost:3000
    }
  },
複製代碼
  • 經過配置hot: true 和 webpack.HotModuleReplacementPlugin() 能夠及時更新css樣式而不刷新頁面!
  • 若是更變js代碼,保證其它代碼的狀態不發生變化,則須要另外加一段代碼,以下:
+ if (module.hot) {
+   module.hot.accept('./print.js', function() {
+     // 檢測到print.js中的更改時,咱們告訴webpack接受更新後的模塊。
+     console.log('Accepting the updated printMe module!');
+     printMe();
+   })
+ }
複製代碼
  • 注意: 爲何改變css代碼不須要添加 module.hot.accpet 代碼呢? 緣由是由於 css-loader 已經幫咱們處理過這一步了,js 代碼就須要咱們本身來添加 HMR 了。在使用 vue 的時候,vue-loader 幫咱們實現了 js HMR 這一塊了,因此也不用咱們本身實現了。react 則是藉助了 babel-preset 來幫咱們實現了 js HMR.

webpack-dev-middleware

經過webpack-dev-middleware 配合 express 能夠本身搭建一個簡單的webpack-dev-server,經過node來運行webpack,代碼以下:webpack

server.js

const webpack = require('webpack')
const middleware = require('webpack-dev-middleware')
const express = require('express')
const config = require('./webpack.config.js')

// webpack 編譯器
const compiler = webpack(config)

const app = express()

app.use(
  middleware(compiler, {
    // webpack-dev-middleware options
    publicPath: config.output.publicPath
  })
)

app.listen(3000, () => {
  console.log('Example app listening on port 3000!')
})
複製代碼

使用babel處理ES6

進入官網,打開 setup, 進入webpack,查看相關文檔git

// npm install --save-dev babel-loader @babel/core
module: {
  rules: [
    {
        test: /\.js$/,
        exclude: /node_modules/, // 不包含
        loader: "babel-loader" // webpack 和 babel 作通訊的橋樑
    }
  ]
}

// 還須要配置options 或者 .babelrc 文件
npm install @babel/preset-env --save-dev

{
    test: /\.js$/,
    exclude: /node_modules/, // 不包含
    loader: "babel-loader", // webpack 和 babel 作通訊的橋樑
    options: {
        "presets": ["@babel/preset-env"]
    }
}
// 可是,這只是將ES6 轉 ES5,有一些語法好比promiss,map等,低版本仍是不認識,這就要使用 @babel/polyfill 了
複製代碼
  • @babel/polyfill
// 提供polyfill是爲了方便,可是您應該將它與@babel/preset-env和useBuiltIns選項一塊兒使用
// 這樣它就不會包含並不是老是須要的整個polyfill。不然,咱們建議您手動導入各個填充。
npm install --save @babel/polyfill
// 而後在代碼的頂部引入:
import "@babel/polyfill";
"presets": [["@babel/preset-env"], {
    targets: {
      chrome: "67", // chrome 版本大於67的,就不須要將ES6轉ES5了,由於chrome對ES6已經支持的很好了
      "ie": "11"
    },
    useBuildIns: "usage" // useBuiltIns選項,若是設置成"usage",那麼將會自動檢測語法幫你require你代碼中使用到的功能。也不須要額外引入@babel/polyfill 了
}]
複製代碼

經過 .babelrc 來聲明 參考文檔es6

  • @babel/plugin-transform-runtime

參考文獻github

  1. 避免屢次編譯出helper函數:
  2. 這裏的 @babel/runtime 包就聲明瞭全部須要用到的幫助函數,而 @babel/plugin-transform-runtime 的做用就是將全部須要helper函數的文件,依賴@babel/runtime包
  3. 解決@babel/polyfill提供的類或者實例方法污染全局做用域的狀況。
// npm install --save-dev @babel/plugin-transform-runtime
// npm install --save @babel/runtime-corejs2

.babelrc

{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": 2, // 表明須要使用corejs的版本; npm install --save @babel/runtime-corejs2
        "helpers": true,
        "regenerator": true,
        "useESModules": false // 按需引入babel/polyfill (注入低版本的polyfill)
      }
    ]
  ]
}
複製代碼

webpack打包React

安裝包 npm i @babel/preset-env @babel/preset-react -Dweb

tree shaking

tree shaking 是一個術語,一般用於描述移除 JavaScript 上下文中的未引用代碼(dead-code)。它依賴於 ES2015 模塊語法的 靜態結構 特性,例如 import 和 export。不支持 require 的引入方式。這是由於 ES 模塊的引入方式是靜態的,而require 的引入方式是動態的。

  • 配置
  1. 將 mode 配置選項設置爲 development 以確保 bundle 是未壓縮版本
mode: "development",
optimization: {
    usedExports: true // 查看那些模塊被使用了,使用了的就打包
}

// 還要再 package.json 中配置一個屬性

"sideEffects": false

// "side effect(反作用)" 的定義是,在導入時會執行特殊行爲的代碼,而不是僅僅暴露一個 export 或多個 export。舉例說明,例如 polyfill,它影響全局做用域,而且一般不提供 export"sideEffects": ["@babel/polyfill", "*.css"] // 不對 @babel/polyfill和全部引入的css文件 做tree shaking

複製代碼

注意: 在開發環境下,使用tree shaking 其實仍是會將沒有使用的模塊打包進 bundle.js 中,只不過會提醒你那些模塊沒有使用。若是使用 mode: production; 那咱們就不須要使用 optimization 配置項了,而且將設置 devtool: cheap-module-source-map。可是 sideEffects 仍是要使用。

區分打包(dev and prod)

npm i webpack-merge -D 用來合併 webpack 模塊

  1. webpack.common.js,存放 dev 和 prod 公共的配置
  2. webpack.dev.js, 開發環境的配置
  3. webpack.prod.js,生產環境的配置
  4. 咱們能夠將以上的webpack配置文件放入到 build 文件夾中統一管理

看看 webpack.prod.js 的用法

webpack.prod.js

const merge = require('webpack-merge')
const devConfig = require('webpack.common.js')

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

module.exports = merge(prodConfig, merge)
複製代碼

而後修改 package.json 中的配置

// 啓動dev
"dev": "webpack-dev-server --config ./build/webpack.dev.js"

// 啓動prod
"build": "webpack --config ./build/webpack.prod.js"
複製代碼

注意: 若是咱們的webpack配置放在build文件夾中,而且咱們的配置中使用 clean-webpack-plugin,那麼它的配置也須要發生改變

new CleanWebpackPlugin(['dist']) // 指的是刪除當前目錄下的 dist 目錄,可是咱們的 dist 目錄須要放在 build 文件夾同級目錄下。

// 能夠這樣作 : 在 github 上搜索 clean-webpack-plugin
new CleanWebpack(['dist'], {
    root: path.resolve(__dirname, '../') // 指定根路徑
})
複製代碼

Code Splitting (代碼分割)

代碼分離能夠用於獲取更小的 bundle,以及控制資源加載優先級,若是使用合理,會極大影響加載時間。

經常使用的代碼分離方法有三種:

  1. 入口起點:使用 entry 配置手動地分離代碼。(優勢:最簡單最直觀。缺點:對咱們的示例來講毫無疑問是個嚴重問題,由於咱們在 ./src/index.js 中也引入過 lodash,這樣就形成在兩個 bundle 中重複引用。)

  2. 防止重複:使用 SplitChunksPlugin 去重和分離 chunk。

optimization: { // 這裏能夠配置 code splitting, 還能夠配置 tree shaking 時須要的 usedExports
    splitChunks: {
        chunks: 'all'
    }
}
複製代碼

將index.js 和 another_module.js 中的 lodash 庫抽離出來了。

  1. 動態導入:

經過模塊中的內聯函數調用來分離代碼。 咱們再也不使用 statically import(靜態導入) lodash,而是經過 dynamic import(動態導入) 來分離出一個 chunk。 查看官網demo

環境變量

想要消除 開發環境 和 生產環境 之間的 webpack.config.js 差別,你可能須要環境變量(environment variable)。***參考文檔***

  • 配置
  1. 對於咱們的 webpack 配置,有一個必需要修改之處。一般,module.exports 指向配置對象。要使用 env 變量,你必須將 module.exports 轉換成一個函數:
const path = require('path');

module.exports = env => {
  // Use env.<YOUR VARIABLE> here:
  console.log('NODE_ENV: ', env.NODE_ENV); // 'local'
  console.log('Production: ', env.production); // true

  return {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };
};
複製代碼
  1. 既然能夠在配置文件中接收到 env ,那麼咱們就能夠將上面的 mode 區分部分從新修改一下,怎麼修改呢?以下:
// 在上面的 環境區分中,咱們在 webpack.common.js 中引入 devConfig、prodConfig、webpack-merge,而後經過 env 來判斷當前的打包命令是開發環境仍是生產環境。

module.exports = env => {
    if (env && env.production) {
        return merge(commonConfig, prodConfig);
    } else {
        return merge(commonConfig, devConfig);
    }
}
複製代碼

Library 打包

除了打包應用程序,webpack 還能夠用於打包 JavaScript library. 參考文檔

  1. libraryTarget 還能夠賦值爲 'this' 或者 'window' | 'global',意味着將 library 掛載到 全局上
  2. 若是咱們本身寫的庫中引入的第三方庫,好比lodash.js,可是咱們不但願它打包到咱們的庫中,那麼應該怎麼辦呢?咱們須要配置以下參數
// webpack.config.js
externals: ['lodash']
// 不打包lodash,可是別人引入咱們的庫的時候,就必須引入lodash.js,由於咱們的庫依賴lodash
複製代碼
  • 發佈本身的庫到 npm 上,給別人使用
  1. 首先配置咱們的package.json文件
  2. 到 npm 官網上註冊咱們的帳號
  3. npm adduser (添加用戶名和密碼)
  4. npm publish (將咱們本身的庫發佈到 npm 倉庫上去)
  5. 注意,庫的名字必定要特別,不能和npm 倉庫上的庫同名

漸進式網絡應用程序 ( PWA )

咱們經過搭建一個簡易 server 下,測試下這種離線體驗。這裏使用 http-server package:npm install http-server --save-dev 參考

{
  "scripts": {
+    "build": "webpack",
+    "start": "http-server dist"
  }
}
複製代碼
  • 添加 Workbox
npm install workbox-webpack-plugin --save-dev

const WorkboxPlugin = require('workbox-webpack-plugin');

    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
+       title: '漸進式網絡應用程序'
+     }),
+     new WorkboxPlugin.GenerateSW({
+       // 這些選項幫助快速啓用 ServiceWorkers
+       // 不容許遺留任何「舊的」 ServiceWorkers
+       clientsClaim: true,
+       skipWaiting: true
+     })
    ]
複製代碼
  • 註冊 Service Worker

接下來咱們註冊 Service Worker,使其出場並開始表演。經過添加如下注冊代碼來完成此操做:

import _ from 'lodash';
  import printMe from './print.js';

+ if ('serviceWorker' in navigator) {
+   window.addEventListener('load', () => {
+     navigator.serviceWorker.register('/service-worker.js').then(registration => {
+       console.log('SW registered: ', registration);
+     }).catch(registrationError => {
+       console.log('SW registration failed: ', registrationError);
+     });
+   });
+ }
複製代碼

TypeScript

TypeScript 是 JavaScript 的超集,爲其增長了類型系統,能夠編譯爲普通 JavaScript 代碼 參考

npm install --save-dev typescript ts-loader
複製代碼
  1. 在webpack.config.js中配置
  2. 須要建立一個tsconfig.json
{
  "compilerOptions": {
    "outDir": "./dist/", // 當前目錄下的dist
    "noImplicitAny": true, // 
    "module": "es6", // 使用ES Module 引入方式
    "target": "es5", // 轉換成 ES5 的代碼
    "jsx": "react",
    "allowJs": true // 容許引入 js 文件
  }
}
複製代碼
  1. 使用typescript時咱們可能使用外部的庫,可是外部的庫是沒法利用typescript自動檢測的特性的。這個時候咱們就須要安裝這些庫的類型文件,好比lodash, 咱們如何安裝它的自動檢測類型文件呢?
npm i @types/lodash -D // 經過 @types/ + 庫的名字便可
複製代碼

devServer.proxy

// 請求到 /api/users 如今會被代理到請求 http://localhost:3000/api/users
devServer: {
    proxy: {
        '/api': {
            target: 'http://localhost:3000',
            pathRewrite: {
                'header.json': 'demo.json' //咱們請求header.json的時候,轉而去請求demo.json
            },
            secure: false, // 默認狀況下,不接受運行在 HTTPS 上,且使用了無效證書的後端服務器。若是你想要接受,修改配置
            bypass: function(req, res, proxyOptions) { // 攔截,若是請求的是 html 類型的數據,就直接返回 html文件,不進行轉發
                if (req.headers.accept.indexOf("html") !== -1) {
                    console.log("Skipping proxy for browser request.");
                    return "/index.html";
                }
            }
        }
    }
}
複製代碼

devServer.historyApiFallback

當使用 HTML5 History API 時,任意的 404 響應均可能須要被替代爲 index.html.在作單頁面路由的時候,須要配置此選項。

devServer: {
    historyApiFallback: true
}
複製代碼

ESlint配置

約束代碼規範的工具,團隊開發尤爲重要

// 安裝
npm i eslint -D

// 初始化配置文件
npx eslint --init
複製代碼

  1. 使用Airbnb公司的規範,咱們須要另外覆蓋一些配置

  1. 在webpack中配置 eslint-loader

npm i eslint-loader -D (會下降打包的速度哦 ! 能夠在options中配置 cache 屬性) 參考官網配置

注意: 在webpack.devServer中配置一個屬性:

devServer: {
    overlay: true, // 將報錯顯示在頁面上
}
複製代碼

webpack性能提高

  • 升級版本
  1. 好比webpack,node,npm,yarn
  • 儘可能少使用 Loader
  1. 在使用 babel-loader編譯 js 代碼的時候,使用 exclude 或者 include來忽略掉 node_modules下的文件,由於這個文件下的文件都是編譯過了的,就不必再次讓 babel-loader 來編譯了。
  • 儘可能少使用 Plugin
  1. 好比在開發環境中,咱們就不必使用壓縮css或者js 代碼了。
  • 合理配置resolve
  1. resolve.alias: 建立 import 或 require 的別名,來確保模塊引入變得更簡單
  2. resolve.extensions: 自動解析肯定的擴展。默認值爲:extensions: [".js", ".json"], 可以使用戶在引入模塊時不帶擴展:import File from '../path/to/file'.也能夠添加 'jsx' 之類的文件
  3. resolve.mainFields: 經過配置這個選項,來引入能夠像省略 index.js 這樣的文件。好比咱們引入import index from './src',其實就是引入的src下的index.js,咱們也能夠配置其它的文件,好比 main.js,hello.js ...

  • DllPlugin

DLLPlugin 和 DLLReferencePlugin 用某種方法實現了拆分 bundles,同時還大大提高了構建的速度。

  1. 實現的邏輯是,將第三方庫只打包一次並打包成一個文件 (也能夠打包成多個文件,配置 entry 屬性就好了),而後將其緩存起來,以後再作打包的時候,再也不去node_modules中尋找了
  2. 建立一個 webpack.dll.js 配置文件

經過 webpack.DllPlugin來產生一個第三方庫的源代碼文件和一個映射文件(manifest.json) 3. npm i add-asset-html-webpack-plugin -D

將咱們打包好的第三方庫源代碼引入到 HtmlWebpackPlugin 生成的 index.html 中

  1. 使用 webpack.DllRefrencePlugin 來分析代碼

這個插件是在 webpack 主配置文件(webpack.common.js)中設置的。它會結合manifest.json、第三方庫源碼文件以及咱們引入的第三方庫文件作一個分析,若是它發現咱們引入的文件在源碼文件裏面已經有了,它就直接拿過來用了,而不會node_modules裏面找了。

new webpack.DllRefrencePlugin({
    manifest: path.resolve(__dirname, './vendors.manifest.json')
})
複製代碼
  1. 拆分紅多個第三方庫文件

動態的導入多個插件,將 plugins 提取出來,動態push 一些插件進去

  • 控制包文件大小
  1. 還記得 tree shaking 吧?去除冗餘代碼 ...
  2. splitChunks 來將大文件拆分紅多個小文件
  • 多進程打包

webpack 默認是使用 node.js 來運行,即採用的單線程機制打包過程

  1. node裏面的多進程 thread-loader, parallel-webpack, happypack
  • 合理使用 sourceMap

描述越詳細,就越慢哦 ~

  • 結合 stats 分析打包結果

結合分析,查看哪一個模塊打包耗時比較長,作針對性處理

  • 開發環境內存編譯

採用 webpack-dev-server

  • 開發環境剔除無用插件

好比壓縮 css 或者 js 的插件

  1. 這裏咱們須要配置環境變量來區分打包、

多頁面打包配置

本質就是建立多個entry 以及 HtmlWebpackPlugin 來實現的

  1. 好比咱們要打包兩個js文件,而且經過兩個 index.html 分別引入

2. 配置 HtmlWebpackPlugin 插件

3. 若是還有多個 js 文件須要打包,那麼咱們就能夠寫一些邏輯來動態建立,就不須要一個個來寫 HtmlWebpackPlugin了

如何編寫 Loader

其實 loader 就是一些函數(不能使用箭頭函數哦,由於this),接受的參數就咱們的源代碼,經過函數來作一些處理並返回而已。loaderAPI

// myself-loader.js

module.exports = function (source) {
    return something ...
}

// 而後在 module 中配置
module: {
    rules: [
        {
            test: /\.js$/,
            loader: path.resolve(__dirname, './loaders/myself-loader.js')
        }
    ]
}

// 如何傳參?
// 配置 options 以後,在咱們寫的 laoder 裏面經過 this.query 就能獲取到啦 !!!
{
    test: /\.js$/,
    use: [
        loader: path.resolve(__dirname, './loaders/myself-loader.js'),
        options: {
            name: 'alex.cheng'
        }
    ]
}

// 本身定義的 loader 如何像引入 node_modules 裏面 loader 同樣引入呢 ?
resolveLoader: {
    modules: ["node_modules", "./loader"] // 若是在node_modules裏沒找到,就到當前文件loader中去找
}
複製代碼

如何編寫一個Plugin

咱們使用別人的 plugin 的時候都是怎麼使用的呢? 是否是都要 new Plugin ? 因此啊,咱們的 plugin 都是經過構造函數來編寫的。來看一個簡單的例子

// plugin: alex-cheng-webpack-plugin.js

class AlexChengWebpackPlugin {
  constructor() {
    console.log('alex.cheng plugin is excuted !')
  }
}
module.exports = AlexChengWebpackPlugin

// webpack.config.js

const path = require('path')
const AlexChengWebpackPlugin = require('./src/myPlugins/alex-cheng-webpack-plugin')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, './dist/')
  },
  plugins: [
    new AlexChengWebpackPlugin() // 調用就好了啦 !!
  ]
}
複製代碼

看!咱們本身的插件就執行了咯 ! 詳情能夠參考 官網API

相關文章
相關標籤/搜索