前端工程化

模塊化開發

模塊化概述

模塊化是一種主流的組織方式,它經過把咱們的複雜代碼按照功能的不一樣劃分爲不一樣的模塊,單獨的維護這種方式,提升咱們的開發效率,下降維護成本。javascript

模塊化只是思想,不包含具體的實現。css

模塊化演變過程

  • Stage1-文件劃分方式:基於文件劃分的方式
    • 污染全局做用域
    • 命名衝突問題
    • 沒法管理模塊依賴關係
    • 早期模塊化徹底依靠約定
  • Stage2-命名空間方式:每一個模塊只暴露一個全局對象,因此模塊成員都掛載到這個對象中
    • 沒有私有空間,模塊成員能夠被外部修改
    • 模塊之間的依賴關係沒有獲得解決
  • Stage3-IIFE:採用當即執行函數表達式爲模塊提供私有空間
    • 私有成員的概念:須要暴露給外部的成員經過掛載到全局對象上去實現
    • 利用自執行函數的參數做爲依賴聲明去使用,每一個模塊之間的依賴關係變得明顯,利於後期維護

模塊化規範的出現

  • CommenJS規範(NodeJS中提出的一套標準,NodeJS全部的模塊代碼必需要遵循CommenJS規範)
    • 一個文件就是一個模塊
    • 每一個模塊都有單獨的做用域
    • 經過module.exports導出成員
    • 經過require函數載入模塊
    • CommonJS是以同步模式加載模塊
  • AMD(異步的模塊定義規範)
    • Require.js實現了AMD規範,另外它自己也是一個強大的模塊加載器
    • 目前絕大多數第三方庫都支持AMD規範
    • AMD使用起來相對複雜
    • 模塊JS文件請求頻繁
  • CMD(通用的模塊定義規範)
    • Sea.js+CMD

模塊化標準規範

  • CommonJS in Node.jshtml

    • Node內置的模塊系統,沒有任何的環境問題
  • Es Modules in Browers前端

    • 最主流的前端模塊化規範
    • 絕大數的瀏覽器都支持Es Modules這個特性(原生支持意味着能夠直接使用這個特性)

ES Modules特性

  • 經過給script添加type=module的熟悉,就能夠以ES Module的標準執行其中的JS代碼
  • 自動採用嚴格模式,忽略use strict。
  • 每一個ES Module都是運行在單獨的私有做用域中
  • ESM是經過CORS的方式請求外部JS模塊
  • ESM的Script標籤會延遲執行腳本,不會阻礙頁面元素的顯示

ES Modules導入導出的注意事項

  • 導出的成員並非一個字面量對象,語法和字面量對象很像。導入的語法很ES6的結構很像,可是它不是一個結構。
  • ES Modules模塊的導出的不是成員的值,而是這個值存放的地址,在外部拿到的這個成員會受當前內部模塊修改的影響
  • 在外部導入一個模塊的成員只是一個只讀的成員,並不能去修改它們

ES Modules導入用法和注意事項

  • 不能省略.js的擴展名,和CommonJS是有區別的
  • 不能像CommonJS那樣載入目錄的方式載入index.js,須要提供完整的路徑
  • 後期可使用打包工具打包咱們的模塊時能夠省略擴展名和省略index.js等操做
  • 相對路徑下的./在ESModule是不能省略的,否則會認爲是在加載第三方的模塊,和Common相同。
  • 可使用完整的URL加載模塊
  • 可使用*號方式把全部的成員所有提取出來,可使用as的方式把全部導出的成員放入到一個對象當中
  • 不能嵌套在if和函數當中
  • import函數能夠動態導入模塊,這個函數返回的是一個Promise,當這個模塊加載完成以後會自動執行then當中所指定的回調函數,模塊的對象能夠經過參數去拿到

ES Modules瀏覽器環境Polyfill

  • IE不兼容ES Modules,能夠藉助編譯工具將ES6的代碼編譯成ES5的方式才能正常工做
  • Polyfile可讓瀏覽器直接去支持ES Modules當中絕大多少的特性
  • npm的模塊可使用unpkg.com這個網站提供的cdn服務去拿到它下面全部的js文件。
    • unpkg.com/browser-es-…
    • 工做原理:經過es-module-loader讀取出來交給babel去轉換,從而讓咱們的代碼能夠正常工做
  • promise-polyfill可讓瀏覽器支持Promise(瀏覽器支持能夠忽略)
  • 支持ESM的瀏覽器用了polyfill會被執行兩次,元素是瀏覽器自己支持被執行了一次,而後es-modules的polyfill也會執行一次,能夠藉助script標籤的新屬性nomodule去解決
    • nomodule標籤只會在不支持esmodules的瀏覽器環境中工做
  • 總結:
    • 這種兼容ES Modules的方式只適合咱們本地測試或者開發階段
    • 生成階段千萬不要去用,它的原理是在運行階段動態的去解析腳本,效率太低
    • 生成階段應該預先去把這些代碼編譯出來,讓它們在瀏覽器中直接運行工做

ES Modules in Node.js:支持狀況

  • Node8.5版本事後,能夠原生的使用ESM去編寫咱們的代碼,還處於實驗階段
  • CommonJS規範與ESM規範差距較大,目前仍是處於過渡狀態
  • 可使用node --experimental-modules xxx.mjs去啓用這個試驗特性執行文件
  • 內置模塊兼容了ESM的提取成員方式
  • 第三方模塊都是導出默認成員

ES Modules in Node.js:與CommonJS模塊交互

  • CommonJS模塊始終只會導出一個默認成員,ESM不能直接提取成員,注意import不是解構導出對象
  • Node環境當中不能在CommonJS模塊中經過require載入ES Modules
  • 總結:
    • ES Modules中能夠導入CommonJS模塊
    • CommonJS中不能導入ES Modules模塊
    • CommonJS始終只會導出一個默認成員
    • 注意import不是解構導出對象,它只是一個固定的用法,去提取導出模塊當中的命名成員

ES Modules in Node.js:與CommonJS之間的差別

  • CommonJSvue

    • require:加載模塊函數
    • module:模塊對象
    • exports:導出對象別名
    • __filename:當前文件的絕對路徑
    • __dirname: 當前文件所在的目錄
  • ES Modulesjava

    • import: 加載模塊函數
    • export: 導出對象別名
    • import.meta.url :當前所工做文件的文件URL地址
    • 經過url模塊中的fileURLToPath方法能夠獲得__filename當前文件的絕對路徑
    • 經過path模塊中的dirname方法獲得__dirname當前文件所在的目錄
  • ESM中沒有CommonJS中的那些模塊全局變量node

ES Modules in Node.js:新版本進一步支持ESM

  • 能夠在package.json下添加type字段,將type字段設置爲module,這樣咱們項目下全部的js文件都會以ESM規範去工做,就不用將擴展名改爲mjs了。
  • 將type設置成module以後,就沒法直接使用CommonJS規範了,這時候想要使用CommonJS的話須要將文件的擴展名改爲.cjs

ES Modules in Node.js:Babel兼容方案

  • babel是一款主流的JavaScript編譯器,它能夠用來將咱們使用了一些新特性的代碼編譯成當前環境所支持的代碼

Webpack打包

模塊打包工具的由來

  • 模塊化解決了咱們在代碼開發當中的代碼組織問題,隨着咱們引入模塊化,咱們的應用會產生新的問題。
    • ES Modules存在環境兼容問題
    • 模塊文件過多,網絡請求頻繁
    • 全部的前端資源都須要模塊化
      • 隨着應用的日益複雜,html/css文件也會面臨這些問題
    • 開發階段包含新特性的代碼轉換爲絕大多數環境支持的代碼,解決環境兼容的問題
    • 將散落的模塊文件打包在一塊兒,解決模塊文件過多,請求頻繁的問題
    • 支持不一樣類型的資源模塊(.js/.css/.scss/.hbs/.png/.ts)

模塊打包工具概要

  • webpack
    • 模塊打包器(Module bundler)
      • 將零散的模塊代碼打包到同一個js文件當中
    • 模塊加載器(Loader)
      • 在代碼中有環境兼容問題的代碼經過模塊加載器進行編譯轉換
    • 代碼拆分(Code Splitting)
      • 將應用當中全部的代碼按照咱們的須要去打包,不用擔憂把全部的代碼所有打包在一塊兒產生的文件過大的問題,能夠把應用程序初次運行的時候所必須的一些模塊打包在一塊兒,其它的模塊單獨的進行存放,實際須要使用的時候去異步加載這些模塊。
    • 資源模塊(Assets Module)
      • 支持以模塊化的方式去載入任意類型的資源文件
    • 打包工具解決的是前端總體的模塊化,並不單指JavaScript模塊化

快速上手

webpack做爲目前最主流的前端模塊打包器,提供了一整套前端項目模塊化方案。react

  • 安裝:yarn add webpack webpack-cli --dev
  • 編譯:yarn webpack

配置文件

webpack4之後的版本它支持零配置的方式直接啓動打包,打包過程會按照約定src/index.js=>dist/main.jsjquery

  • 在項目根目錄下添加一個webpack.config.js文件,這個文件是運行在Node環境中的,咱們使用CommonJS規範
const path = require('path')

module.exports = {
    //去指定webpack打包入口文件的路徑
    entry: './src/main.js',
    //設置輸出文件的配置
    output: {
        //輸出文件的名稱
        filename: 'bundle.js',
        //輸出文件的路徑(絕對路徑)
        path: path.join(__dirname, 'dist')
    }
}
複製代碼

工做模式

webpack4新增工做模式作法,這種用法大大的簡化了webpack配置的複雜程度,能夠把它理解成針對於不一樣環境的幾組預設配置webpack

  • 生產模式:yarn-webpack (默認webpack --mode production)
    • 自動啓用優化插件,將咱們的代碼進行壓縮
  • 開發模式:webpack --mode development
    • 自動優化打包的速度,添加一些調試過程當中須要的輔助到咱們代碼當中
  • none模式:webpack --mode none
    • 進行最原始的打包,不會進行任何額外的處理
module.exports = {
    //工做模式
    mode: 'development'
}
複製代碼

打包結果運行原理

總體生成的代碼是一個當即執行的函數,這個函數接受一個modules的參數,調用時傳入了一個數組

展開這個數組,數組中的每個元素都是一個參數列表相同的函數,這裏的函數對於的是源代碼當中的模塊,每個模塊最終都會包裹到這些函數當中,從而實現模塊的私有做用域

資源模塊加載

Loader是Webpack的核心特性,藉助於Loader就能夠加載任何類型的資源。

以加載css爲例,首先安裝css-loader來轉換css文件,在安裝style-loader將css-loader轉換事後的結果經過style標籤的形式添加到頁面上,webpack配置以下:

// 配置對象
    module: {
        //其餘資源模塊加載規則
        rules: [{
            //匹配打包過程當中遇到的文件路徑
            test: /.css$/,
            //匹配文件打包過程當中用的loader 配置了多個loader執行順序是從後往前執行
            use: [
                'style-loader', //把css-loader轉換後的結果經過style標籤的形式添加到頁面上
                'css-loader' //處理css文件的加載器
            ]
        }]
    }
複製代碼

導入資源模塊

webpack的打包入口通常是javascript文件,通常打包入口是應用程序的運行入口,目前而言,前端應用中的業務是由JavaScript來驅動的。

import './heading.css'

export default () => {
    const element = document.createElement('h2')

    element.textContent = 'Hello world'
    element.classList.add('heading')
    element.addEventListener('click', () => {
        alert('Hello webpack')
    })

    return element
}
複製代碼
  • 傳統的作法當中咱們將樣式和行爲單獨分開和引入,webpack建議咱們在js文件當中載入css,咱們編寫代碼的過程中根據代碼須要動態導入資源文件,真正須要資源的不是應用,而是此時正在編寫的代碼
  • JavaScript驅動整個前端應用,在實現業務功能的過程中可能須要圖片或者樣式等資源文件,若是創建了這個依賴關鍵,邏輯合理,JS確實須要這些資源文件,確保上線資源不缺失,都是必要的
  • 學習新事物不是學會它的全部用法你就能提升,由於這些東西照着文章誰均可以,要搞清楚它爲何這樣設計,基本上算是出道了
    • 新事物的思想纔是突破點

加載器

文件加載器

  • file-loader 處理文件加載器

  • 文件加載器的工做過程

    • webpack在打包時遇到咱們的圖片文件,根據咱們配置文件當中的配置匹配到對應的文件加載器,此時文件加載器開始工做,它先將咱們導入的文件拷貝到輸出目錄,而後將輸出目錄的路徑做爲當前模塊的返回值返回,這樣對於咱們的應用來講所須要的資源就被髮布出來了,同時咱們能夠經過模塊的導出成員拿到咱們資源的訪問路徑

    • {
      	test: /.png$/,
      	use: 'file-loader' //文件加載器
      }
      複製代碼

URI加載器

  • Data URIs是一種當前URL就能表示文件內容的方式,這種URL中的文本就已經包含了文件內容,咱們在使用這種URL的時候就不會去發送任何的HTTP請求

  • url-loader Data URI加載器

    • {
      	test: /.png$/,
      	use: 'url-loader', //Data URLs加載器
      	//配置選項
      	options: {
      		limit: 10 * 1024 // 只將10kb如下的文件用url-loader處理
      	}
      }
      複製代碼
  • 最佳實踐

    • 小文件使用Data URLs,減小請求次數
    • 大文件單獨提早存放,提升加載速度
    • 超出10KB文件單獨提取存放
    • 小於10KB文件轉換爲Data URLs嵌入代碼
  • 注意事項:對於超出大小的文件url loader會去調用file loader,因此仍是要安裝fileloader

經常使用分類加載器

webpack的資源加載器相似生活當中工廠裏面的生產車間,它是用來處理和加工打包過程看成的資源文件

  • 編譯轉換類
    • 這種類型的loader會把咱們的模塊轉換成JavaScript的代碼
  • 文件操做類
    • 文件操做類型的加載器會把咱們的資源模塊拷貝到輸出目錄,同時將文件的訪問路徑向外導出
  • 代碼檢查類
    • 對咱們所加載到的資源文件進行校驗,它的目的統一咱們的代碼風格,提升咱們的代碼質量

Webpack與ES 2015

因爲webpack默認就能處理咱們代碼當中的import/export,因此很天然的有人認爲webpack會自動編譯es6的代碼,由於模塊打包須要,因此處理import/export,除此以外並不能處理代碼當中其餘的es6特性,若是咱們須要在打包過程看成處理其餘es6特性的轉換,咱們須要爲js文件添加一個額外的編譯性loader

  • babel-loader,一個編譯型loader,用來處理es6特性的轉換

    • {
          test: /.js$/,
              use: {
                  loader: 'babel-loader', //處理es6代碼看成的新特性
                      options: {
                          //babel只是一個轉換js代碼的平臺,在平臺轉換過程當中須要額外的插件
                          presets: ['@babel/preset-env'] 
                      }
              }
      }
      複製代碼
  • webpack只是打包工具

  • 加載器能夠用來編譯轉換代碼

加載資源的方式

  • 遵循ES Modules標準規範的import聲明
  • 遵循CommonJS標準的Require函數
  • 遵循AMD標準的define函數和require函數
  • Loader加載非JavaScript也會觸發資源加載
    • 樣式代碼中的@import指令和url函數
    • HTML代碼中圖片標籤的src屬性
  • *樣式代碼中的@import指令和url函數
  • *HTML代碼中圖片標籤的src屬性
{
    test: /.html$/,
    use: {
            loader: 'html-loader', //html解析器
            options: {
            // html加載的時候對頁面上的一些屬性作一些額外處理
            attrs: [
                'img:src', //默認
                'a:href'
            ]
        }
    }
}
複製代碼

核心工做原理

  • 在咱們的項目當中通常都會散落着各類各樣的代碼和資源文件,webpack會根據咱們的配置找到其中的一個文件做爲打包的入口,而後順着爲咱們的入口文件當中的代碼,根據咱們代碼中出現的import/require以內的語句解析推斷出來這個文件所依賴的資源模塊,分別去解析每一個資源模塊的資源依賴,最後會造成整個項目中全部文件之間依賴關係的依賴樹,有了這個依賴樹事後會遞歸這個依賴樹,找到每一個節點所對應的資源文件,根據咱們配置文件當中的rules屬性去找到這個模塊所對應的加載器,而後交給對應的加載器去加載這個模塊,最後將加載到的結果放入到bundle.js(配置的輸出文件路徑)當中,從而實現整個項目的打包
  • Loader機制是Webpack的核心,若是沒有loader就沒有辦法去實現各類資源文件的加載,對於webpack來講就只是一個打包或者合併代碼的工具了

開發一個Loader

  • markdown-loader 在代碼當中直接導入markdown文件

    • 輸入就是資源文件的內容
    • 輸出是處理完成以後的結果
  • 實現方式

    const marked = require('marked') //markdown解析模塊
    
    module.exports = source => {
        const html = marked(source)
            //返回的類型必定要是js代碼
            // return 'console.log("hello ~")'
            //直接拼接html當中存在的換行符和內部的引號拼接在一塊兒可能引發語法錯誤
            // return `module.exports="${html}"` 
            //CommonJS方式導出字符串
            // return `module.exports=${JSON.stringify(html)}`
            //ES Modules方式導出
            // return `export default ${JSON.stringify(html)}`
    
        //返回html字符串交給下一個loader處理
        return html
    }
    複製代碼
    {
         test: /.md$/,
         use: [
             'html-loader',
             './markdown-loader'
         ]
     }
    複製代碼
  • 工做原理

    • Loader負責資源文件從輸入到輸出的轉換
    • Loader是一種管道的概念,咱們能夠將咱們這次Loader的結果交給下一個Loader處理
    • 對於同一個資源可一次使用多個Loader
      • css-loader=>style-loader

插件機制介紹

  • 插件機制是webpack另一個核心特性,目的是爲了加強webpack在項目自動化的能力
  • loader專一實現資源模塊的加載,從而實現總體項目的打包
  • plugin解決其餘自動化工做
    • 清除dist目錄
    • 拷貝靜態文件至輸出目錄
    • 壓縮輸出代碼
  • webpack+plugin實現了大多前端工程化絕大多數常常用到的部分

自動清除輸出目錄插件

  • clean-webpack-plugin 自動清除輸出目錄
//配置插件
plugins: [
    //清理輸出目錄
    new CleanWebpackPlugin()
]
複製代碼

自動生成HTML插件

  • html-webpack-plugin 自動生成使用bundle.js的HTML
//配置插件
plugins: [
    //清理輸出目錄
    new CleanWebpackPlugin(),
    //自動生成index.html
    new HtmlWebpackPlugin({
        title: 'Webpack Plugin Sample',
        meta: {
        	viewport: 'width=device-width'
        },
        template: './index.html'
    }),
    //用於生成about.html
    new HtmlWebpackPlugin({
        filename: 'about.html'
    })
]
複製代碼

插件使用總結

  • copy-webpack-plugin 將文件拷貝到輸出目錄
  • 社區當中提供了成百上千的插件,咱們並不須要所有認識,當咱們有特殊需求時,咱們只須要提取需求當中的關鍵詞,而後去github上搜索它們,雖然每一個插件的做業不經相同,可是它們的用法上幾乎相似。

開發一個插件

  • 相比於loader,plugin擁有更寬的能力範圍,loader只是加載模塊的環境去工做,plugin的工做範圍幾乎能夠觸及到webpack工做的每個環節
  • plugin經過鉤子機制實現
  • webpack要求plugin必須是一個函數或者是一個包含apply方法的對象
  • 總結
    • 插件是經過在生命週期的鉤子中掛載函數實現擴展
class MyPlugin {
    apply(compiler) {
        console.log('My Plugin 啓動')
        compiler.hooks.emit.tap('MyPlugin', compilation => {
            //compilation能夠理解爲這次打包的上下文
            for (const name in compilation.assets) {
                // console.log(compilation.assets[name].source())
                //判斷是不是js文件
                if (name.endsWith('.js')) {
                    //獲取文件的內容
                    const contents = compilation.assets[name].source()
                        //將註釋替換成空
                    const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
                        //將最終結果覆蓋原有的內容當中
                    compilation.assets[name] = {
                        //返回新的內容
                        source: () => withoutComments,
                        //返回內容大小,這個方式是webpack內部要求必須的方法
                        size: () => withoutComments.length
                    }
                }
            }
        })
    }
}
複製代碼
//配置插件
plugins: [
    //自定義插件 刪除生成的js文件當中的註釋
    new MyPlugin()
]
複製代碼

開發體驗問題

  • 以HTTP Server運行,而不是以文件的方式進行預覽
  • 自動編譯+自動刷新
  • 提供Source Map支持

自動編譯

  • watch工做模式
    • 監聽文件變化,自動從新打包
    • yarn webpack --watch

自動刷新瀏覽器

  • BrowserSync 這個工具能夠幫咱們實現自動刷新的功能
    • 須要同時使用兩個工具
    • 開發效率上下降,開發過程中webpack會不斷寫入磁盤,browserSync又從磁盤中讀取出來,這個過程中一次就會多出兩步的讀寫操做

Webpack Dev Server

  • Webpack Dev Server是Webpack官方推出的一個開發工具
  • 提供用於開發的HTTP Server
  • 集成自動編譯和自動刷新瀏覽器等功能

靜態資源訪問

  • Webpack Dev Server默認會將構建結果輸出的文件所有做爲開發服務器的資源文件,只要經過webpack打包輸出的文件都能被訪問到,但若是還有一些靜態資源也須要做爲開發資源被訪問的化,須要額外的告訴webpack dev server
  • contentBase 額外爲開發服務器指定查找資源目錄

代理API

  • 跨域資源共享(CORS),使用CORS的前提是API必須支持,並非任何狀況下API都應該支持

  • 同源部署(域名端口協議一致)

  • 開發階段接口跨域

    • 開發服務器中配置代理服務,把接口服務代理到本地開發服務的地址
  • webpack dev server支持配置代理

  • 用法

    • 目標:將Github API代理到開發服務器
    // webpack dev server的配置選項
    devServer: {
        //靜態資源文件路徑
        contentBase: ['./public'],
            //代理對象
            proxy: {
                '/api': {
                    //http://localhost:8080/api/users =>https://api.github.com/api/users
                    target: 'https://api.github.com',
                        //http://localhost:8080/api/users =>https://api.github.com/users
                        //代理路徑重寫
                        publicPath: {
                            '^/api': ''
                        },
                        //不能使用 localhost:8080 做爲請求 github 的主機名
                        changeOrigin: true //以實際代理請求的主機名去請求
                }
            }
    }
    複製代碼

Source Map

  • 運行的代碼與源代碼之間徹底不一樣,若是須要調試應用,錯誤信息沒法定位,調試和報錯都是基於運行代碼
  • Sourece Map(源代碼地圖),能夠經過SourceMap文件逆向解析源代碼
  • Sourece Map解決了源代碼與運行代碼不一致所產生的問題

配置Source Map

//配置開發過程當中的輔助工具
devtool: 'source-map'
複製代碼
  • 截止到目前,webpack支持12種不一樣的方式,每種方式的效率和效果各不相同
devtool 構建速度 從新構建速度 生產環境 品質(quality)
(none) 很是快速 很是快速 yes 打包後的代碼
eval 很是快速 很是快速 no 生成後的代碼
eval-cheap-source-map 比較快 快速 no 轉換過的代碼(僅限行)
eval-cheap-module-source-map 中等 快速 no 原始源代碼(僅限行)
eval-source-map 比較快 no 原始源代碼
eval-nosources-source-map
eval-nosources-cheap-source-map
eval-nosources-cheap-module-source-map
cheap-source-map 比較快 中等 yes 轉換過的代碼(僅限行)
cheap-module-source-map 中等 比較慢 yes 原始源代碼(僅限行)
inline-cheap-source-map 比較快 中等 no 轉換過的代碼(僅限行)
inline-cheap-module-source-map 中等 比較慢 no 原始源代碼(僅限行)
inline-source-map no 原始源代碼
inline-nosources-source-map
inline-nosources-cheap-source-map
inline-nosources-cheap-module-source-map
source-map yes 原始源代碼
hidden-source-map yes 原始源代碼
hidden-nosources-source-map
hidden-nosources-cheap-source-map
hidden-nosources-cheap-module-source-map
hidden-cheap-source-map
hidden-cheap-module-source-map
nosources-source-map yes 無源代碼內容
nosources-cheap-source-map
nosources-cheap-module-source-map

eval模式的Source Map

  • eval是js當中的一個函數,它能夠運行字符串當中的js代碼,默認狀況下運行在臨時的虛擬機環境當中
  • 不會生成source map文件,構建速度最快,只能定位源代碼文件的名稱,而不知道具體的行列信息

devtool模式對比

  • eval
    • 將咱們的模塊代碼放到eval函數當中去執行,而且經過source url去標註文件的路徑,這種模式下沒有生成source map,它只能定位哪一個文件出了錯誤
  • eval-sourece-map
    • 一樣使用eval函數去執行模塊代碼,它除了幫咱們定位錯誤的文件,還能幫咱們定位到行和列的信息,對比eval模式它生成了source map
  • cheap-eval-sourece-map
    • 閹割版的eval-sourece-map,生成的source map只有行的信息,沒有列的信息,但速度會更快
    • es6轉換夠後的結果
  • cheap-module-eval-sourece-map
    • 和cheap-eval-sourece-map相似,不一樣的是它定位的源代碼就是咱們實際編寫的源代碼,沒有通過轉換的
  • cheap-source-map
    • 沒有eval意味着沒有用eval的方式去執行模塊代碼
    • 沒有module意味着是通過loader處理事後的代碼
  • inline-source-map
    • 和普通的source map模式同樣
    • 普通模式是以物理文件地址方式存在
    • inline-source-map使用的是dataurl方式去嵌入到咱們的代碼當中,體積會變大不少
  • hidden-source-map
    • 這種模式在構建過程中生成了map文件,可是代碼當中並無經過註釋的方式去引入這個文件
    • 開發第三方包的時候比較有用
  • nosources-source-map
    • 沒有源代碼,但提供了行列信息,爲了在生產環境當中不會暴露源代碼的狀況
  • eval:是否使用eval執行模塊代碼
  • cheap:Source Map是否包含行信息
  • module:是否可以獲得Loader處理以前的源代碼

選擇Source Map模式

  • 開發模式
    • cheap-module-eval-sourece-map
      • 代碼每行不會超過80個字符
      • 通過Loader轉換事後的差別較大,須要調試源代碼
      • 首次打包速度慢無所謂,重寫打包相對較快
  • 生產模式
    • none
      • Source Map會暴露源代碼
      • 調試是開發階段的事情,生成環境不建議使用source map
  • 理解不一樣模式的差別,適配不一樣的環境

自動刷新的問題

  • 問題:頁面總體刷新,頁面以前的操做狀態會丟失
  • 需求:頁面不刷新的前提下,模塊也能夠及時刷新

HMR體驗

  • Hot Module Replacement(模塊熱替換/熱更新)
  • 熱拔插
    • 在一個正在運行的機器上隨時插拔設備,而咱們機器的運行狀態不會受插拔設備的影響,而插上的設備能夠當即開始工做
    • 電腦上的USB端口能夠熱拔插
  • 模塊熱替換
    • 能夠在應用程序運行的過程當中實時替換某個模塊,應用運行狀態不受影響
    • 自動刷新致使頁面狀態丟失,熱替換隻將修改的模塊實時替換至應用中
  • HMR是Webpack中最強大的功能之一,極大程度提升了開發者的工做效率

開啓HMR

  • HMR集成在webpack-dev-server中

    • webpack-dev-server --hot開啓特性
    • 也能夠在配置文件當中配置開啓特性
    //熱更新插件
    new webpack.HotModuleReplacementPlugin()
    複製代碼

HMR疑問

  • Webpack中的HMR並不能夠開箱即用
  • Webpack中的HMR須要手動處理模塊熱替換邏輯
  • 爲何樣式文件的熱更新開箱即用?
    • 樣式文件是通過loader處理的,在style-loader處理樣式文件的過程當中就已經自動了熱更新,因此不須要咱們額外作手動的操做
  • 憑什麼樣式能夠自動處理,腳本文件要手動處理?
    • 樣式文件處理事後只須要把css及時替換到頁面當中,能夠覆蓋到以前的文件從而實現熱更新
    • 編寫的腳本文件是沒有任何的規律的,webpack在面對這些毫無規律的JS模塊不知道如何去處理這些更新事後的模塊,沒有辦法幫咱們實現一個通用狀況的模塊替換方案
    • 個人項目沒有手動處理,JS照樣能夠熱替換?
      • 使用了vue-cli/create react等腳手架工具,框架下的開發,每種文件都是有規律的,框架提供的就是一些規則
      • 經過腳手架建立的項目內部都集成了HMR方案
  • 總結:咱們須要手動處理JS模塊熱更新後的熱替換

使用HMR API

  • module.hot.accept('./editor', () => {
        console.log('editor模塊更新了,須要這裏手動處理熱更新')
    })
    複製代碼

處理JS模塊熱替換

//存儲最後一次更新的值
let lastEditor = editor
module.hot.accept('./editor', () => {
    console.log('editor模塊更新了,須要這裏手動處理熱更新', lastEditor.value)
    //拿到編輯的內容
    const value = lastEditor.value
    //移除原來的元素
    document.body.removeChild(editor)
    //建立一個新的元素
    const newEditor = createEditor()
    //將原來的只添加到新元素的值當中 避免原來的值丟失
    newEditor.value = value
    //將新元素追加到頁面
    document.body.appendChild(newEditor)
    //記錄最新的元素 不然下次找不到這個元素了
    lastEditor = newEditor
})
複製代碼
  • webpack根本沒有辦法去提供一個通用的替換方案

處理圖片模塊熱替換

//圖片的熱處理替換
module.hot.accept('./01.png', () => {
    img.src = background
    console.log(background)
})
複製代碼

HMR注意事項

  • 處理HMR的代碼報錯會致使自動刷新,控制檯錯誤信息就會被清除,不易察覺
  • 沒啓用HMR的狀況下,HMR API會報錯
  • 代碼中多了一些與業務無關的代碼

生產環境優化

  • 生成環境和開發環境有很大的差別
  • 生成環境注重運行效率
  • 開發環境注重開發效率
  • 模式(mode)
  • 爲不一樣的工做環境建立不一樣的配置

不一樣環境下的配置

  • 配置文件根據環境不一樣導出不一樣配置
  • 一個環境對應一個配置文件
/** * 不一樣的環境返回不一樣的配置 * @param {*} env CLI傳遞的環境名參數 * @param {*} argv 運行CLI過程當中所傳遞的全部參數 */
module.exports = (env, argv) => {
    //開發環境的配置
    const config = {
        //工做模式
        mode: 'none',
        //去指定webpack打包入口文件的路徑
        entry: './src/main.js',
        //設置輸出文件的配置
        output: {
            //輸出文件的名稱
            filename: 'bundle.js',
            //輸出文件的路徑(絕對路徑)
            path: path.join(__dirname, 'dist'),
            //打包事後的文件具體存放位置
            // publicPath: 'dist/'
        },
        // webpack dev server的配置選項
        devServer: {
            //開啓熱更新 報錯會從新刷新瀏覽器,不易調試
            hot: true,
            //不管代碼是否被處理了熱替換,瀏覽器都不會自動刷新
            hotOnly: true,
            //靜態資源文件路徑
            contentBase: ['./public'],
            //代理對象
            proxy: {
                '/api': {
                    //http://localhost:8080/api/users =>https://api.github.com/api/users
                    target: 'https://api.github.com',
                    //http://localhost:8080/api/users =>https://api.github.com/users
                    //代理路徑重寫
                    publicPath: {
                        '^/api': ''
                    },
                    //不能使用 localhost:8080 做爲請求 github 的主機名
                    changeOrigin: true //以實際代理請求的主機名去請求
                }
            }
        },
        //配置開發過程當中的輔助工具
        devtool: 'eval',
        // 配置對象
        module: {
            //其餘資源模塊加載規則
            rules: [{
                    //匹配打包過程當中遇到的文件路徑
                    test: /.css$/,
                    //匹配文件打包過程當中用的loader 配置了多個loader執行順序是從後往前執行
                    use: [
                        'style-loader', //把css-loader轉換後的結果經過style標籤的形式添加到頁面上
                        'css-loader' //處理css文件的加載器
                    ]
                },
                {
                    test: /.png$/,
                    use: {
                        loader: 'url-loader', //Data URLs加載器
                        //配置選項
                        options: {
                            limit: 10 * 1024 // 只將10kb如下的文件用url-loader處理
                        }
                    }
                }, {
                    test: /.html$/,
                    use: {
                        loader: 'html-loader', //html解析器
                        options: {
                            // html加載的時候對頁面上的一些屬性作一些額外處理
                            attrs: [
                                'img:src', //默認
                                'a:href'
                            ]
                        }
                    }
                }, {
                    test: /.md$/,
                    use: [
                        'html-loader',
                        './markdown-loader'
                    ]
                },
                // {
                // test: /.js$/,
                // use: {
                // loader: 'babel-loader', //處理es6代碼當中的新特性
                // options: {
                // presets: ['@babel/preset-env'] //babel只是一個轉換js代碼的平臺,在平臺轉換過程當中須要額外的插件
                // }
                // }
                // }
            ]
        },
        //配置插件
        plugins: [
            //清理輸出目錄
            //new CleanWebpackPlugin(),
            //用於拷貝文件到輸出目錄 開發階段最好不要使用這個插件 影響效率
            // new CopyWebpackPlugin({
            // patterns: [
            // // 'public/**',
            // 'public'
            // ]
            // }),
            //自動生成index.html
            // new HtmlWebpackPlugin({
            // title: 'Webpack Plugin Sample',
            // meta: {
            // viewport: 'width=device-width'
            // },
            // filename: 'index.html'
            // }),
            //用於生成about.html
            new HtmlWebpackPlugin({
                filename: 'index.html'
            }),
            //自定義插件 刪除生成的js文件當中的註釋
            new MyPlugin(),
            //熱更新插件
            new webpack.HotModuleReplacementPlugin()
        ]
    }

    if (env === 'production') {
        console.log('生成環境')
        config.mode = 'production'
        config.devtool = false
        config.plugins = [...config.plugins, new CleanWebpackPlugin()]
    } else {
        console.log('開發環境')
    }

    return config
}
複製代碼
// 開發環境
yarn webpack

// 生產環境
yarn webpack --env production
複製代碼

不一樣環境的配置文件

  • 經過判斷環境名參數去返回不一樣的配置對象這種方式只適用於中小型項目,一旦項目變得複雜,配置文件也變得複雜起來
  • 對於大型項目建議使用不一樣環境對應不一樣配置文件來實現

DefinePlugin

  • 爲代碼注入全局成員
//爲代碼注入全局成員
new webpack.DefinePlugin({
    //符合JS語法的代碼
    API_BASE_URL: '"https://api.example.com"'
})
複製代碼

體驗Tree Shaking

  • 檢測代碼中未引用的代碼,而後移除掉它們
  • 在生產模式下自動開啓

使用Tree Shaking

  • Tree Shaking不是指某個配置選項,它是一組功能搭配使用後的效果

  • production模式下自動開啓

  • 其餘模式開啓

    //集中配置webpack內部的一些優化功能
    optimization: {
        //在輸出結果中只導出被外部使用了的成員
        usedExports: true,
        //開啓代碼壓縮功能
        minimize: true
    }
    複製代碼

合併模塊

  • concatenateModules
    • 儘量將全部模塊合併到一塊兒輸出到一個函數中,即提高了運行效率,又減小了代碼的體積

Tree Shaking與Babel

  • Tree Shaking的實現前提是ES Module
    • 由Webpack打包的代碼必須使用ESM
    • 爲了轉換代碼中的ECMScript新特性,不少時候使用babel-loader去處理JS,babel轉換咱們的代碼時有可能處理掉咱們使用的ES Modules,把它們轉換成了CommonJS,取決於咱們有沒有使用轉換ESM的插件

sideEffects

  • sideEffects容許咱們經過配置的方式來標識咱們的代碼是否有反作用,從而爲Tree Shaking提供更大的壓縮空間
  • 反作用:模塊執行時除了導出成員以外所做的事情
  • sideEffects通常用於npm包標記是否有反作用

sideEffects注意

  • 確保你的代碼真的沒有反作用,不然webpack打包時會誤刪掉那些有反作用的代碼

代碼分割

webpack將咱們全部的代碼都打包到一塊兒,若是咱們的應用程序很是複雜,模塊很是多的狀況下,那麼咱們的打包結果就會特別的大,咱們的應用程序開始工做時並非每一個模塊在啓動時都是必要的,比較合理的方案是分包,按需加載,這樣就能大大提升咱們應用程序的相應速度和運行效率

多入口打包

  • 適用於傳統的多頁應用程序,一個頁面對應一個打包入口,公共部分單獨提取

提取公共模塊

  • 多入口打包存在一個小問題,不一樣的打包入口當中必定會存在一些公用的部分

    //把全部的公共模塊提取到單獨的bundle當中
    splitChunks: {
        chunks: 'all'
    }
    複製代碼

動態導入

  • 須要用到某個模塊時,在加載這個模塊
  • 動態導入的模塊會被自動分包

魔法註釋

  • 若是須要給bundle命名的化可使用webpack提供的魔法註釋去實現
  • 特有格式:/* webpackChunkName: '名稱' */這樣就能夠給分包或者bundle起名字了
  • 若是名稱相同,他們會被打包在一塊兒

MiniCssExtractPlugin

  • 能夠將css代碼從打包結果當中提取出來
  • 經過這個插件能夠實現css模塊的按需加載

OptimizeCssAssetsWebpack

  • 能夠壓縮咱們的樣式文件

輸出文件名 Hash

  • 部署前端資源文件時,會開啓靜態資源緩存,對於用戶的瀏覽器而言,它就能夠緩存咱們的靜態資源文件,提升咱們總體應用程序的相應速度

  • 生產模式下,文件名使用Hash,一旦資源文件發生改變,文件名稱也會發生變化

  • hash:整個項目當中有任何一個地方發生改動,這一次打包過程中的hash值都會發生變化

  • chunkhash:在打包過程中只要是同一路的打包,chunkhash都是相同的

  • contenthash:根據輸出文件的內容生成的hash值

  • 能夠經過佔位符的方式指定生成hash的長度,例如:[contenthash:8]

規範化標準

規範化介紹

  • 爲何要有規範標準
    • 軟件開發須要多人協同
    • 不一樣開發者具備不一樣的編碼習慣和喜愛
    • 不一樣的喜愛增長項目維護成本
    • 每一個項目或者團隊須要明確統一的標準
  • 哪裏須要規範化標準
    • 代碼、文檔、甚至是提交日誌
    • 開發過程當中人爲編寫的成果物
    • 代碼標準化規範最爲重要
  • 實施規範化的方法
    • 編碼前人爲的標準約定
    • 經過工具實現Lint

常見的規範化實現方式

  • ESLint工具使用
  • 定製ESLint效驗規則
  • ESLint對TypeScript的支持
  • ESLint結合自動化工具或者Webpack
  • 基於ESLint的衍生工具
  • Stylelint工具的使用

ESLint介紹

  • 最爲主流的JavaScript Lint工具 監測JS代碼質量
  • ESLint很容易統一開發者的編碼風格
  • ESLint能夠幫助開發者提高編碼能力

安裝

  • 初始化項目
  • 安裝ESLint模塊爲開發依賴
  • 經過CLI命令驗證安裝結果

快速上手

  • ESLint檢查步驟
    • 編寫「問題」代碼
    • 使用eslint執行檢測
    • 完成eslint使用配置
  • 具體使用
    • yarn add eslint --dev
    • yarn eslint init
    • yarn eslint [文件名稱]
    • yarn eslint [文件名稱] --fix
      • fix參數能夠自動解決絕大多數代碼風格的問題

配置文件解析

  • env關鍵字可使用的環境,能夠同時開啓多個環境

    • browser - 瀏覽器環境中的全局變量。
    • node - Node.js 全局變量和 Node.js 做用域。
    • commonjs - CommonJS 全局變量和 CommonJS 做用域 (用於 Browserify/WebPack 打包的只在瀏覽器中運行的代碼)。
    • shared-node-browser - Node.js 和 Browser 通用全局變量。
    • es6 - 啓用除了 modules 之外的全部 ECMAScript 6 特性(該選項會自動設置 ecmaVersion 解析器選項爲 6)。
    • worker - Web Workers 全局變量。
    • amd - 將 require()define() 定義爲像 amd 同樣的全局變量。
    • mocha - 添加全部的 Mocha 測試全局變量。
    • jasmine - 添加全部的 Jasmine 版本 1.3 和 2.0 的測試全局變量。
    • jest - Jest 全局變量。
    • phantomjs - PhantomJS 全局變量。
    • protractor - Protractor 全局變量。
    • qunit - QUnit 全局變量。
    • jquery - jQuery 全局變量。
    • prototypejs - Prototype.js 全局變量。
    • shelljs - ShellJS 全局變量。
    • meteor - Meteor 全局變量。
    • mongo - MongoDB 全局變量。
    • applescript - AppleScript 全局變量。
    • nashorn - Java 8 Nashorn 全局變量。
    • serviceworker - Service Worker 全局變量。
    • atomtest - Atom 測試全局變量。
    • embertest - Ember 測試全局變量。
    • webextensions - WebExtensions 全局變量。
    • greasemonkey - GreaseMonkey 全局變量。
  • extends

    • 共享配置,一個配置文件能夠被基礎配置中的已啓用的規則繼承
  • parserOptions

    • ESLint 容許你指定你想要支持的 JavaScript 語言選項。默認狀況下,ESLint 支持 ECMAScript 5 語法。你能夠覆蓋該設置,以啓用對 ECMAScript 其它版本和 JSX 的支持。

    • ecmaVersion - 默認設置爲 3,5(默認), 你可使用 六、七、八、9 或 10 來指定你想要使用的 ECMAScript 版本。你也能夠用使用年份命名的版本號指定爲 2015(同 6),2016(同 7),或 2017(同 8)或 2018(同 9)或 2019 (same as 10)

    • sourceType - 設置爲 "script" (默認) 或 "module"(若是你的代碼是 ECMAScript 模塊)。

    • ecmaFeatures
      複製代碼

      - 這是個對象,表示你想使用的額外的語言特性:

      • globalReturn - 容許在全局做用域下使用 return 語句
      • impliedStrict - 啓用全局 strict mode (若是 ecmaVersion 是 5 或更高)
      • jsx - 啓用 JSX
      • experimentalObjectRestSpread - 啓用實驗性的 object rest/spread properties 支持。(**重要:**這是一個實驗性的功能,在將來可能會有明顯改變。 建議你寫的規則 不要 依賴該功能,除非當它發生改變時你願意承擔維護成本。)
  • rules

    • 配置每一個校驗規則的開啓或者關閉
  • globals

    • 額外的聲明代碼中可使用的全局成員

配置註釋

結合自動化工具

  • 集成以後,ESLint必定會工做
  • 與項目統一,管理更加方便

Prettier的使用

  • 近兩年來使用較多的通用的代碼格式化工具
  • yarn prettier . --write

Git Hooks介紹

  • 代碼提交至倉庫以前未執行lint工做
  • 經過Git Hooks在代碼提交前強制lint
  • Git Hook也稱爲git鉤子,每一個鉤子都對應一個任務
  • 經過shell腳本能夠執行鉤子任務觸發時要具體執行的操做

ESLint結合Git Hooks

  • Husky能夠實現GitHooks的使用需求,提交以前強制驗證咱們的代碼
  • lint-staged 驗證以後能夠作一些其餘操做,例如提交代碼
相關文章
相關標籤/搜索