webpack4學習筆記(三)

前言

這是我花了幾個星期學習webpack4的學習筆記。內容不夠細,由於一些相對比較簡單的,就隨意帶過了。但願文章能給你們帶來幫助。若有錯誤,但願及時指出。例子都在learn-webpack倉庫上。若是你從中有所收穫的話,但願你能給個人github點個starjavascript

library

當你要開發第三方庫供別人使用時,就須要用到librarylibraryTarget這兩個配置了。html

libraryvue

output: {
    filename: 'library.js',
    library: 'library',
    libraryTarget: 'umd'
},

library: 當配置了這個library參數後,會把library這個key對應的value即上面代碼library掛載到全局做用域中。htmlscript標籤引入,能夠經過訪問全局變量library訪問到咱們本身開發的庫。java

libraryTarget:這個默認值爲var。意思就是讓library定義的變量掛載到全局做用域下。固然還有瀏覽器環境的window,node環境的global,umd等。當設置了windowglobal,library就會掛載到這兩個對象上。當配置了umd後,你就能夠經過import,require等方式引入了。node

externalsreact

exterals是開發公共庫很重要的一個配置。當你的公共庫引入了第三方庫的時候,公共庫會把該第三方庫也打包進你的模塊裏。當使用者也引入了這個第三方庫後,這個時候打包就會又打了一份第三方庫進來。jquery

所在在公共模塊庫中配置以下代碼webpack

externals: {
    // 前面的lodash是個人庫裏引入的包名 好比 import _ from 'lodash'
    // 後面的lodash是別人業務代碼須要注入到他本身模塊的lodash 好比 import lodash from 'lodash',注意不能import _ from 'lodash',由於配置項寫了lodash 就不能import _
    lodash: 'lodash'
},

前面的lodash是個人庫裏引入的包名 好比 import _ from 'lodash',後面的lodash是別人業務代碼須要注入到他本身模塊的lodash 好比 import lodash from 'lodash',注意不能import _ from 'lodash',由於配置項寫了lodash 就不能import _ios

本人作了個試驗,當本身開發的包不引入lodash,業務代碼中也不引入lodash,那麼打包業務代碼的時候,webpack會把lodash打進你業務代碼包裏。git

固然externals,配置還有多種寫法,以下

externals: {
    lodash: {
        commonjs: 'lodash',
        commonjs2: 'lodash',
        amd: 'lodash',
        root: '_'
    }
}

externals: ['lodash', 'jquery']

externals: 'lodash'

具體請參考官網externals

發佈本身開發的npm包

學了上面的配置後,就須要學習下如何將本身的包發佈到npm倉庫上了。

  • package.json 的入口要改爲dist目錄下的js文件如: "main": "./dist/library.js"
  • 註冊npm帳號。npm會發送一份郵件到你的郵箱上,點擊下里面的連接進行激活。
  • 命令行輸入npm login 進行登陸,或者npm adduser 添加帳號
  • npm publish

當出現以下提示表明發佈成功

// 當出現相似以下代碼時,表示你已經發布成功
➜  library git:(master) ✗ npm publish
+ atie-first-module-library@1.0.0

遇到的問題:

當你遇到npm ERR! you must verify your email before publishing a new package說明你尚未激活你的郵箱,去郵箱裏點擊下連接激活下就ok了

當你已經登陸了提醒npm ERR! 404 unauthorized Login first,這個時候你就要注意下你的npm源了,看看是否設置了淘寶源等。記得設置回來npm config set registry https://registry.npmjs.org/

PWA

http-server

workbox-webpack-plugin

相信不少朋友都有耳聞過PWA這門技術,PWAProgressive Web App的英文縮寫, 翻譯過來就是漸進式加強WEB應用, 是Google 在2016年提出的概念,2017年落地的web技術。目的就是在移動端利用提供的標準化框架,在網頁應用中實現和原生應用相近的用戶體驗的漸進式網頁應用。

優勢:

  1. 可靠 即便在不穩定的網絡環境下,也能瞬間加載並展示
  2. 快速響應,而且 動畫平滑流暢

應用場景:

當你訪問正常運行的服務器頁面的時候,頁面正常加載。可當你服務器掛了的時候,頁面就沒法正常加載了。

這個時候就須要使用到pwa技術了。

這裏我編寫最簡單的代碼重現下場景:

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
    mode: 'production',
    entry: './index.js',
    output: {
        filename: 'bundle.js'
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin()
    ]
}

// index.js
console.log('this is outer console')

// package.json
{
  "name": "PWA",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack --config webpack.config.js",
    "start": "http-server ./dist"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "clean-webpack-plugin": "^2.0.1",
    "html-webpack-plugin": "^3.2.0",
    "http-server": "^0.11.1",
    "webpack": "^4.30.0",
    "webpack-cli": "^3.3.1",
  }
}

執行下npm run build

.
├── bundle.js
└── index.html

爲了模擬服務器環境,咱們安裝下http-server

npm i http-server -D

配置下package.json"start": "http-server ./dist"

執行npm run start來啓動dist文件夾下的頁面

這個時候控制檯會正常打印出'this is outer console'

當咱們斷開http-server服務後,在訪問該頁面時,頁面就報404了

這個時候就須要使用到pwa技術了

使用步驟:

安裝: npm i workbox-webpack-plugin -D

webpack配置文件配置:

// webpack.config.js
const {GenerateSW} = require('workbox-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
    mode: 'production',
    entry: './index.js',
    output: {
        filename: 'bundle.js'
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin(),
        new GenerateSW({
            skipWaiting: true, // 強制等待中的 Service Worker 被激活
            clientsClaim: true // Service Worker 被激活後使其當即得到頁面控制權
        })
    ]
}

這裏咱們寫一個最簡單的業務代碼,在註冊完pwa以後打印下內容:

// index.js
console.log('this is outer console')

 // 進行 service-wroker 註冊
 if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker
            .register('./service-worker.js')
            .then(registration => {
                console.log('====== this is inner console ======')
                console.log('SW registered: ', registration);
            })
            .catch(registrationError => {
                console.log('SW registration failed: ', registrationError);
            });
    });
}

執行下打包命令:npm run build

.
├── bundle.js
├── index.html
├── precache-manifest.e21ef01e9492a8310f54438fcd8b1aad.js
└── service-worker.js

打包以後會生成個service-worker.jsprecache-manifest.e21ef01e9492a8310f54438fcd8b1aad.js

接下來再重啓下http-server服務:npm run start

頁面將會打印出

this is outer console
====== this is inner console ======
...

而後咱們再斷開http-server服務

刷新下頁面,居然打印出了相同的代碼。說明pwa離線緩存成功。

typescript

使用webpack打包ts文件,就須要安裝ts-loader

安裝:npm i ts-loader typescript -D

webpack.config.js文件中添加解析typescript代碼的loader

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

module.exports = {
    mode: 'production',
    entry: './src/index.ts',
    output: {
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                loader: 'ts-loader',
                exclude: /node_modules/
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin()
    ]
}

配置了webpack.config.js還不行,還得在根目錄文件下新增個.tsconfig.json文件

{
    "compilerOptions": {
        "outDir": "./dist/", // 默認解析後的文件輸出位置
        "noImplicitAny": true, // 存在隱式 any 時拋錯
        "module": "es6", // 表示這是一個es6模塊機制
        "target": "es5", // 表示要講ts代碼轉成es5代碼
        "allowJs": true // 表示容許引入js文件。TS 文件指拓展名爲 .ts、.tsx 或 .d.ts 的文件。若是開啓了 allowJs 選項,那 .js 和 .jsx 文件也屬於 TS 文件
    }
}

新建index.ts

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");

let button = document.createElement('button');
button.textContent = "Say Hello";
button.onclick = function() {
    alert(greeter.greet());
}

document.body.appendChild(button);

執行打包命令,訪問打包後的頁面,頁面正常執行。

當須要使用lodash等庫時,

需安裝:npm i @types/lodash -D

修改頁面代碼 引入 lodash

import * as _ from 'lodash'

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");

let button = document.createElement('button');
button.textContent = "Say Hello";
button.onclick = function() {
    alert(_.join(['lodash', greeter.greet()], '-'));
}

document.body.appendChild(button);

提醒:ts使用的包,可經過https://microsoft.github.io/TypeSearch 這個網址去查對應的包使用指南

使用WebpackDevServer實現請求轉發

當咱們工做本地開發某一個需求的時候,須要將這塊需求的請求地址轉發到某個後端同事的本地服務器或某個服務器上,就須要用到代理。而後其餘頁面的請求都走測試環境的請求。那麼咱們該怎樣攔截某個請求,並將其轉發到咱們想要轉發的接口上呢?

這個時候就須要用到webpack-dev-server

主要看devServer配置:

// webpack.config.js
const CleanWebpackPlugin = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    mode: "development",
    entry: './index.js',
    output: {
        filename: 'bundle.js'
    },
    devServer: {
        contentBase: './dist',
        open: true,
        hot: true
    },
    plugins: [
        new HtmlWebpackPlugin(),
        new CleanWebpackPlugin()
    ]
}
// package.json

scripts: {
  "server": "webpack-dev-server"
}
// index.js
import axios from 'axios'

const div = document.createElement('div')
div.innerHTML = 'hahahha'
div.addEventListener('click', () => {
    alert('hahah')
    axios.get('/list').then(res => {
        console.log(res)
    })
})

document.body.appendChild(div)

在寫一個本地啓動的服務端代碼

const express = require('express')
const app = express()

app.get('/api/list', (req, res) => {
    res.json({
        success: true
    })
})

app.listen(8888, () => {
    console.log('listening localhost:8888')
})

執行npm run server命令,瀏覽器會自動打開頁面。點擊div後,會發起請求。

瀏覽器提示http://localhost:8080/api/list 404 (Not Found),表示該接口不存在。

由於咱們webpack啓動靜態資源服務器默認端口爲8080,因此他求會直接請求到8080的/api/list接口。因此會提示找不到該接口。

爲了解決這個問題,咱們就須要將該請求從8080端口代理到8888端口(也就是咱們本身本地啓動的服務)

配置webpack.config.js

這裏我只展現devServer代碼

// webpack.config.js
devServer: {
    contentBase: './dist',
    open: true,
    hot: true,
    proxy: {
        '/api': {
            target: 'http://localhost:8888'
        }
    }
},

配置devServerproxy字段的參數,將請求/api開頭的請求都轉發到http://localhost:8888,

經過這個方法能夠解決一開始提到的本地開發的時候,只想把部分接口轉發到某臺部署新需求的服務器上。好比當你這個項目請求不少,不一樣接口部署在不一樣的端口或者不一樣的服務器上。那麼就能夠經過配置第一個路徑,來區分不一樣的模塊。並轉發到不一樣的服務上。如:

// webpack.config.js
devServer: {
    contentBase: './dist',
    open: true,
    hot: true,
    proxy: {
        '/module1': {
            target: 'http://localhost:8887'
        },
        '/module2': {
            target: 'http://localhost:8888'
        },
        '/module3': {
            target: 'http://localhost:8889'
        }
    }
},

當你要代理到某個https的接口上,就須要設置secure: false

// webpack.config.js
devServer: {
    proxy: {
        '/api': {
            target: 'https://other-server.example.com',
            secure: false
        }
    }
}
target: '', // 代理的目標地址
secure: false, // 請求https的須要設置
changeOrigin: true,  // 跨域的時候須要設置
headers: {
  host: 'http://www.baidu.com', //修改請求域名
  cookie: ''
}
...

其餘關於devServer的配置詳見devServer

WebpackDevServer解決單頁面路由404問題

相信你們都是開發過vue或者react單頁面帶路由的應用。這裏就忽略業務代碼,介紹下devServerhistoryApiFallback參數

devServer: {
  historyApiFallback: true, // 當設置爲true時,切換路由就不會出現路由404的問題了
}

詳見historyApiFallback

eslint

安裝eslint: npm i eslint -D

目錄下新建.eslintrc.json文件。

environment: 指定腳本的運行環境

globals: 腳本在執行期間訪問的額外全局變量。

rules: 啓動的規則及其各自的錯誤級別。

解析器選項: 解析器選項

編輯你的eslint的規則

{
    "parserOptions": {
        "ecmaVersion": 6,
        "sourceType": "module",
        "ecmaFeatures": {
            "jsx": true
        }
    },
    "rules": {
        "semi": 2
    }
}

vscode安裝eslint插件。

配置下webpack.config.js配置。

...
devServer: {
    overlay: true,
    contentBase: './dist',
    hot: true,
    open: true
},
module: {
    rules: [{
        test: /\.js$/,
          exclude: /node_modules/,
        use: ['eslint-loader']
    }]
}
...

eslint-loader是用於檢查js代碼是否符合eslint規則。

這裏devServer中的overlay的做用是,當你eslint報錯的時候,頁面會有個報錯蒙層。這樣就不侷限於編輯器(vscode)的報錯提醒了。

若是js代碼使用了多個loader,那麼eslint-loader必定要寫在最右邊。若是不寫在最後一個的話,需在裏面添加enforce: "pre",這樣無論寫在哪一個位置都會優先使用eslint-loader校驗下代碼規範。

{
    loader: 'eslint-loader',
    options: {
        enforce: "pre",
    }
}

提高webpack打包速度的方法

1. 跟上技術的迭代

  • 升級webpack版本 node版本 npm等版本

2. 儘量少的模塊上應用loader

  • include exclude

3. 儘量少的使用plugin

4. resolve

resolve: {
    extensions: ['.js'],
    alias: {
        'src': path.resolve(__dirname, '../src')
    }
}

extensions: 可讓你import模塊的時候不寫格式,當你不寫格式的時候,webpack會默認經過extensions中的格式去相應的文件夾中找

alias:當你import的路徑很長的時候,最好使用別名,能簡化你的路徑。

好比:import index.js from '../src/a/b/c/index.js'

設置別名:

resolve: {
    alias: {
        '@c': path.resolve(__dirname, '../src/a/b/c')
    }
}

這樣你的import導入代碼就能夠改爲import index.js from '@c/index.js'

5. dllPlugin

咱們先記錄下不使用dll打包時間787ms

Time: 787ms
Built at: 2019-05-04 18:32:29
     Asset       Size  Chunks             Chunk Names
 bundle.js    861 KiB    main  [emitted]  main
index.html  396 bytes          [emitted]

接下來咱們就嘗試使用dll技術

咱們先配置一個用於打包dll文件的webpack配置文件,生成打包後的js文件與描述動態連接庫的manifest.json

// webpack.dll.config.js
const path = require('path')
const webpack = require('webpack')

module.exports = {
    entry: {
        vendor: ['jquery', 'lodash'] // 要打包進vendor的第三方庫
    },
    output: {
        filename: '[name].dll.js', // 打包後的文件名
        path: path.resolve(__dirname, './dll'), // 打包後存儲的位置
        library: '[name]_[hash]' // 掛載到全局變量的變量名,這裏要注意 這裏的library必定要與DllPlugin中的name一致
    },
    plugins: [
        new webpack.DllPlugin({ // 用於打包出一個個單獨的動態連接庫文件
            name: '[name]_[hash]', // 引用output打包出的模塊變量名,切記這裏必須與output.library一致
            path: path.join(__dirname, './dll', '[name].manifest.json') // 描述動態連接庫的 manifest.json 文件輸出時的文件名稱
        })
    ]
}

重點:這裏引入的DllPlugin插件,該插件將生成一個manifest.json文件,該文件供webpack.config.js中加入的DllReferencePlugin使用,使咱們所編寫的源文件能正確地訪問到咱們所須要的靜態資源(運行時依賴包)。

配置下package.json文件的scripts: "build:dll": "webpack --config webpack.dll.config.js"

執行下 npm run build:dll

Time: 548ms
Built at: 2019-05-04 18:54:09
        Asset     Size  Chunks             Chunk Names
vendor.dll.js  157 KiB       0  [emitted]  vendor

除了打包出dll文件以外,還得再主webpack配置文件中引入。這裏就須要使用到DllReferencePlugin。具體配置以下:

// webpack.config.js
new webpack.DllReferencePlugin({
  manifest: require('./dll/vendor.manifest.json')
}),

這裏的manifest:須要配置的是你dllPlugin打包出來的manifest.json文件。讓主webpack配置文件經過這個

描述動態連接庫manifest.json文件,讓js導入該模塊的時候,直接引用dll文件夾中打包好的模塊。

看似都配置好了,接下來執行下命令 npm run build

使用dll打包後時間:

Time: 471ms
Built at: 2019-05-04 18:19:49
     Asset       Size  Chunks             Chunk Names
 bundle.js   6.43 KiB    main  [emitted]  main
index.html  182 bytes          [emitted]

直接從最開始的787ms下降到471ms,當你抽離的第三方模塊越多,這個效果就越明顯。

瀏覽器跑下html頁面,會報錯

Uncaught ReferenceError: vendor_e406fbc5b0a0acb4f4e9 is not defined

這是由於index.html還須要經過script標籤引入這個dll打包出來的js文件

咱們若是每次本身手動引入的話會比較麻煩,若是dll文件很是多的話,就不可思議了。

這個時候就須要藉助add-asset-html-webpack-plugin這個包了。

const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')

new AddAssetHtmlPlugin({
  filepath: path.resolve(__dirname, './dll/vendor.dll.js')
})

經過這包,webpack會將dll打包出來的js文件經過script標籤引入到index.html文件中

這個時候你在npm run build,訪問下頁面就正常了

6. 控制包文件大小

tree-shaking 等

7. thread-loader parallel-webpack happypack等多進程打包

8. 合理使用sourceMap

9. 結合stats分析打包結果

藉助線上或者本地打包分析工具

10. 開發環境內存編譯

開發環境的時候不會生成dist文件夾,會直接從內存中讀取,由於內存讀取比硬盤讀取快

11. 開發環境無用插件剔除

相關文章
相關標籤/搜索