模塊化開發(fā)
當(dāng)下最重要的前段開發(fā)范式,“模塊化”是一種思想
模塊化演變過程
早期在沒有工具和規(guī)范的情況下脱篙,對模塊化的落地范式
- Stage 1 - 文件劃分范式
- 污染全局作用域
- 命名沖突
- 無法管理模塊依賴關(guān)系
- 早期模塊化完全依靠約定
- Stage 2 - 命名空間方式舌狗,每個模塊只暴露一個全局對象劳曹,所有模塊成員都掛載到這個對象中
- 模塊成員可以被修改
- Stage 3 - IIFE歼捐,使用立即執(zhí)行函數(shù)表達式(Immediately-Invoked Function Expression)為模塊提供私有空間
模塊化演變過程
模塊化標準 + 模塊加載器
CommonJS規(guī)范
- 一個文件就是一個模塊
- 每個模塊都有單獨的作用域
- 通過module.exports到處成員
- 通過require函數(shù)載入模塊
- CommonJS是已同步模式加載模塊
AMD(Asynchronous Module Definition), require.js
- define函數(shù)跃洛,定義一個模塊
- require函數(shù),載入一個模塊
- 目前絕大多數(shù)第三方庫都支持AMD規(guī)范
- AMD使用起來相對復(fù)雜
- 模塊JS文件請求頻繁
Sea.js + CMD(Common Module Definition)
- Sea.js,淘寶團隊推出的庫蜜宪,類似CommonJS規(guī)范
- 使用上有點類似require.js
模塊化標準規(guī)范
模塊化的最佳實踐
- node.js環(huán)境中虫埂,遵循CommonJS規(guī)范
- 瀏覽器環(huán)境中,遵循ES Modules規(guī)范
ES Modules基本特性
- 自動采用嚴格模式圃验,忽略'use strict'
- 每個ESM模塊都是單獨私有作用域
- ESM是通過CORS去請求外部JS模塊的
- ESM的script標簽回延遲執(zhí)行腳本
ES Modules注意事項
- export {}這是一個固定的語法掉伏,不是es6中的對象簡寫
- import {}這是一個固定的語法,不是es6中的對象解構(gòu)
- 到處得到的是對值的飲用澳窑,模塊內(nèi)部修改了值斧散,外部也會跟著改變
- 導(dǎo)入的成員是只讀成員
ES Modules導(dǎo)出和導(dǎo)入
- export 注意有無default關(guān)鍵字
- 不能省略文件后綴名,不能省略./
- 執(zhí)行某個模塊摊聋,不需要提取模塊中的成員import './module.js'
- 動態(tài)導(dǎo)入模塊鸡捐,可以用全局函數(shù)import()
ES Modules in Node.js
支持情況
- 執(zhí)行文件時,node --experimental-modules index.mjs
- ES Module中可以導(dǎo)入CommonJS模塊
- CommonJS中不能導(dǎo)入ES Module模塊
- CommonJS始終只會導(dǎo)出一個默認成員
- 注意import不是解構(gòu)導(dǎo)出對象
與 CommonJS 模塊的差異
- ESM 中沒有 CommonJS 中的那些模塊全局成員了(require module exports __filename __dirname)
- 利用 import 和 url path 模塊實現(xiàn)
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
console.log(__filename);
const __dirname = dirname(__filename);
console.log(__dirname);
新版本的 node 進一步支持 ESM
- babel是基于插件機制實現(xiàn)的麻裁,核心模塊并不會轉(zhuǎn)換代碼
- 具體轉(zhuǎn)換代碼是通過插件來做的()
- @babel/preset-env 是一個插件的集合箍镜,它包含了最新的JS標準中的所有新特性
- @babel/plugin-transform-modules-commonjs 這才是一個具體的插件
Webpack 打包
打包工具解決的是前端整體的模塊化,并不單指 JavaScript 模塊化
webpack 工作模式
- mode: 'production',
- mode: 'development',
- mode: 'none',
webpack 資源模塊加載
- JS file => Default Loader
- Other file => Other Loader
webpack 導(dǎo)入資源模塊
- JavaScript 驅(qū)動整個前端應(yīng)用
- 在 js 中導(dǎo)入相關(guān)資源模塊悲立,邏輯合理鹿寨,JS 確實需要這些資源文件
- 確保上線資源不缺失,都是必要的
學(xué)習(xí)一個新事物薪夕,不是學(xué)會它的所有用法就能提高,掌握新事物的思想才是突破點赫悄。能夠搞明白這些新事物為什么這樣設(shè)計原献,那就基本上算是出道了。
webpack 文件資源加載器
- JS file => Default Loader => Bundle.js
- 圖片埂淮、字體等資源文件 => File Loader => 文件路徑 => Bundle.js
webpack URL 加載器
協(xié)議 媒體類型和編碼 文件內(nèi)容
data:<mediatype>,<data>
data:text/html;charset-UTF-8,<h1>content</h1>
data:image/png;base64,iVBORw0KGg...SuQmCC
最佳實踐
- 小文件使用 Data URLs姑隅,減少請求次數(shù)
- 大文件單獨提取存放,提高加載速度
{
test: /.png$/,
use: {
loader: 'url-loader',
options: {
limit: 10 * 1024 // 10 KB
}
}
}
- 超出 10KB 文件單獨提取存放
- 小于 10KB 文件轉(zhuǎn)換為 Data URLS 嵌入代碼中
webpack 常用加載器分類
- 編譯轉(zhuǎn)換類(css-loader => 以 JS 形式工作的我 CSS 模塊)
- 文件操作類(file-loader => 導(dǎo)出文件訪問路徑)
- 代碼檢查類(eslint-loader => 檢查通過/不通過)
webpack 加載資源的方式
- 遵循 ES Modules 標準的 import 聲明
- 遵循 CommonJS 標準的 require 函數(shù)
- 遵循 AMD 標準的 define 函數(shù)和 require 函數(shù)
- 樣式代碼中的 @import 執(zhí)行和 url 函數(shù)
- HTML 代碼中圖片標簽的 src 屬性
webpack 核心工作原理
Loader 機制是 Webpack 的核心
webpack 插件機制
- Loader 專注實現(xiàn)資源模塊加載
- Plugin 解決其他自動化工作
- Plugin 用途:
- 打包之前清除 dist 目錄
- 拷貝靜態(tài)文件至輸出目錄
- 壓縮輸出代碼
- 常用的插件:
- clean-webpack-plugin 打包之前清除 dist 目錄
- html-webpack-plugin 用于生成 index.html 文件
- copy-webpack-plugin 拷貝靜態(tài)文件至輸出目錄
開發(fā)一個插件:插件是通過在生命周期的鉤子中掛載函數(shù)實現(xiàn)擴展
如何增強 webpack 開發(fā)體驗
- 自動編譯
- 自動刷新瀏覽器
- webpack-dev-server:繼承了以上特性的工具
Source Map
- 運行代碼與源代碼之間完全不同
- 如果需要調(diào)試應(yīng)用倔撞,或者運行應(yīng)用過程中出現(xiàn)了錯誤讲仰,錯誤信息無法定位
- 調(diào)試和報錯都是基于運行代碼
- Source Map 解決了源代碼與運行代碼不一致所產(chǎn)生的問題
Source Map的方式
webpack 支持 12 種不同的 source-map 方式,每種方式的效率和效果各不相同
不同 devtool 之間的差異
- eval - 是否使用 eval 執(zhí)行模塊代碼
- cheap - Source Map 是否包含行信息
- module - 是否能夠得到 Loader 處理之前的源代碼
選擇合適的 Source Map
- 開發(fā)模式:cheap-module-eval-source-map
- 我的代碼每行不會超過 80 個字符
- 我的代碼經(jīng)過 Loader 轉(zhuǎn)換過后的差異較大
- 首次打包速度慢無所謂痪蝇,重新打包速度較快
- 生產(chǎn)環(huán)境:none / nosources-source-map
- 安全隱患鄙陡,source-map 會暴露源代碼
- 調(diào)試是開發(fā)階段的事情
- 沒有絕對的選擇,理解不同模式的差異躏啰,適配不同的環(huán)境
HMR 體驗
HMR(Hot Module Replacement): 模塊熱替換
- 應(yīng)用運行過程中實時替換某個模塊
- 應(yīng)用運行狀態(tài)不受影響
- 自動刷新會導(dǎo)致頁面狀態(tài)丟失
- 熱替換只將修改的模塊實時替換至應(yīng)用中
開啟 HMR
集成在 webpack-dev-server 中
- webpack-dev-server --hot
- 也可以通過配置文件開啟
HMR 的疑問
- webpack 中的 HMR 并不可以開箱即用
- webpack 中的 HMR 需要手動處理模塊熱替換邏輯
- 為什么樣式文件的熱更新開箱即用趁矾?因為樣式經(jīng)過了 loader 處理,然后只需要替換掉某段 <style></style> 就可以實現(xiàn)
- 我的項目沒有手動處理给僵,JS 照樣可以熱替換毫捣?因為使用了框架,框架下的開發(fā),每種文件都是有規(guī)律的
- 通過腳手架創(chuàng)建的項目內(nèi)部都集成了 HMR 方案
總結(jié):我們需要手動處理 JS 模塊更新后的熱替換
Webpack 生產(chǎn)環(huán)境優(yōu)化
- 生產(chǎn)環(huán)境跟開發(fā)環(huán)境有很大差異
- 生產(chǎn)環(huán)境注重運行效率蔓同,開發(fā)環(huán)境注重開發(fā)效率
- 模式(mode)饶辙,為不同的工作環(huán)境創(chuàng)建不同的配置
Webpack Tree Shaking
- 盡可能的將所有模塊合并輸出到一個函數(shù)中
- 既提升了運行效率,又減少了代碼體積
- Tree Shaking 又被稱為 Scope Hoisting 作用域提升
Webpack Tree Shaking 與 Babel
- Tree Shaking 前提是 ES Modules
- 由 Webpack 打包的代碼必須使用 ESM
- 為了轉(zhuǎn)換代碼中的 ECMAScript 新特性而使用 babel-loader 斑粱,就有可能導(dǎo)致 ESM => CommonJS畸悬,這取決我們有沒有使用轉(zhuǎn)換 ESM 的插件
Webpack 代碼分割
代碼分包
- 所有代碼最終都被打包到一起,bundle 體積過大
- 并不是每個模塊在啟動時都是必要的
- 模塊打包是必要的珊佣,但是應(yīng)用越來越大之后蹋宦,需要進行分包,按需加載
- 有兩種方式:多入口打包咒锻;ESM 動態(tài)導(dǎo)入
多入口打包
- 常用于多頁應(yīng)用程序
- 一個頁面對應(yīng)一個打包入口
- 公共部分單獨提取
動態(tài)導(dǎo)入
- 按需加載冷冗,需要用到某個模塊時,再加載這個模塊
- 可以極大地節(jié)省帶寬和流量
- 無需配置任何地方惑艇,只需要按照 ESM 動態(tài)導(dǎo)入的方式去導(dǎo)入模塊蒿辙,webpack 內(nèi)部會自動處理分包和按需加載
- 使用單頁應(yīng)用開發(fā)框架(React/Vue),在項目中的路由映射組件就可以通過動態(tài)導(dǎo)入實現(xiàn)按需加載
Webpack 魔法注釋
- 使用魔法注釋可以為動態(tài)導(dǎo)入最終打包出來的文件命名
- 命名相同的模塊最終會被打包到一起
Webpack 輸出文件名 Hash
- 一般我們部署前端資源文件時滨巴,都會采用服務(wù)器的靜態(tài)資源緩存
- 開啟緩存的問題:緩存時間過短-效果不明顯思灌,緩存過期時間較長-應(yīng)用發(fā)生了更新重新部署后客戶端因為緩存得不到更新
- 解決上面問題,建議生產(chǎn)模式下恭取,文件名使用 Hash泰偿,文件名不同也就是新的請求,解決了緩存的問題蜈垮,服務(wù)器可以將緩存過期時間設(shè)置足夠長
- 三種 Hash 方式
- hash: 整個項目級別的耗跛,項目中任意一個地方改動,重新打包之后的 hash 值都會改變
- chunkhash: chunk 級別的攒发,同一路的打包 chunkhash 都是相同的
- contenthash: 文件級別的hash调塌,根據(jù)文件內(nèi)容生成的hash值,不同的文件就有不同的值
解決緩存問題的最佳 hash 方式 [contenthash:8]
Rollup
Rollup 概述
- Rollup 與 Webpack作用類似
- Rollup 更為小巧
- 僅僅是一款 ESM 打包器
- Rollup 中并不支持類似 HMR 這種高級特性
- Rollup 的初衷是提供一個充分利用 ESM 各項特性的高效打包器
Rollup 快速上手
# 安裝依賴
yarn add rollup --dev
# 指定打包的入口文件惠猿、打包輸出格式羔砾、輸出結(jié)果路徑,執(zhí)行打包
yarn rollup ./src/index.js --format iife --file dist/bundle.js
Rollup 配置文件
- 在項目根目錄創(chuàng)建 rollup.config.js
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'iife'
}
}
- 執(zhí)行命令 yarn rollup --config 完成打包偶妖,也可以在命令最后跟上文件名
Rollup 使用插件
- 想加載其他類型的資源模塊
- 想導(dǎo)入 CommonJS 模塊姜凄、編譯 ECMAScript 新特性
- Rollup 支持使用插件的方式擴展,插件是 Rollup 唯一擴展途徑
- rollup-plugin-json 加載 json 文件的插件
- rollup-plugin-node-resolve 加載 npm 模塊的插件
- rollup-plugin-commonjs 加載 CommonJS 模塊
Rollup 代碼拆分
使用 Dynamic Imports 動態(tài)導(dǎo)入實現(xiàn)模塊按需加載餐屎,實現(xiàn)代碼拆分/分包
rollup.config.js 修改為:
export default {
input: 'src/index.js',
output: {
// file: 'dist/bundle.js',
// format: 'iife'
dir: 'dist',
format: 'amd'
}
}
Rollup 多入口打包
- 將 rollup.config.js 文件中的 input 改為一個數(shù)組 或者 對象
- 對于以 amd 格式輸出的文件檀葛,不能直接引入到頁面上,需要配合 Require.js 這樣的庫使用
Rollup VS Webpack 選用原則
- 優(yōu)點:
- 輸出結(jié)果更加扁平
- 自動移除未引用的代碼
- 打包結(jié)果依然完全可讀
- 缺點:
- 加載非 ESM 的第三方模塊比較復(fù)雜
- 模塊最終都被打包到一個函數(shù)中腹缩,無法實現(xiàn) HMR
- 瀏覽器環(huán)境中屿聋,代碼拆分功能依賴 AMD
- 選用原則:
- 如果我們正在開發(fā)應(yīng)用程序 => webpack
- 如果我們正在開發(fā)框架或者類庫 => rollup
- 大多數(shù)知名框架 / 庫都在使用 rollup
- 社區(qū)中希望二者共存空扎,webpack 大而全,rollup 小而美
規(guī)范化標準
規(guī)范化標準介紹
規(guī)范化是我們踐行前端工程化中重要的一部分
- 為什么要有規(guī)范會標準润讥?
- 軟件開發(fā)需要多人協(xié)同
- 不同開發(fā)者具有不同的編碼習(xí)慣和喜好
- 不同的喜好會增加項目的維護成本
- 每個項目或者團隊需要明確統(tǒng)一的標準
- 哪里需要規(guī)范化標準转锈?
- 代碼、文檔楚殿、甚至是提交日志
- 開發(fā)過程中人為編寫的成果物
- 代碼標準化規(guī)范最為重要
- 實施規(guī)范化的方法
- 編碼前人為的標準約定
- 通過工具實現(xiàn) Lint
- 常見的規(guī)范化實現(xiàn)方式
- ESLint 工具使用
- 定制 ESLint 校驗規(guī)則
- ESLint 對 TypeScript 的支持
- ESLint 結(jié)合自動化工具或者 Webpack
- 基于 ESLint 的衍生工具
- StyleLint 工具的使用
ESLint 介紹
- 最為主流的 JavaScript Lint 工具撮慨,檢測 JS 代碼質(zhì)量
- ESLint 很容易統(tǒng)一開發(fā)者的編碼風(fēng)格
- ESLint 可以幫助開發(fā)者提升編碼能力
ESLint 快速上手
- 初始化項目,安裝 ESLint 模塊為開發(fā)依賴 npm install eslint -D
- 編寫“問題”代碼脆粥,使用 eslint 執(zhí)行檢測 npx eslint ./01-prepare.js 加上參數(shù) --fix 可以自動修復(fù)格式問題
- 當(dāng)代碼中存在語法錯誤時砌溺,eslint 沒法檢查問題代碼
- 完成 eslint 使用配置
結(jié)合自動化工具
- 集成之后,ESLint 一定會工作
- 與項目統(tǒng)一变隔,管理更加方便
- 結(jié)合 gulp 使用规伐,通過 .pipe(plugins.eslint()) 讓其工作
ESLint 結(jié)合 Webpack
- Webpack 可以通過 loader 機制實現(xiàn) eslint 的檢測工作
- 安裝 eslint eslint-loader
- 在 webpack.config.js 文件配置 eslint-loader 應(yīng)用在 .js 文件中
- 安裝相關(guān)插件,如:eslint-plugin-react
- 修改 .eslintrc.js 的配置
ESLint 檢查 TypeScript
- 初始化項目
- 安裝 eslint typescript
- 初始化 .eslintrc.js 配置文件匣缘,注意當(dāng)詢問 use TypeScript ? 是要選擇 yes
- 執(zhí)行 npx eslint .\index.ts
Stylelint 認識
- 提供默認的代碼檢查規(guī)則
- 提供 CLI 工具猖闪,快速調(diào)用
- 通過插件支持 Sass Less PostCSS
- 支持 Gulp 或 Webpack 集成
- 快速上手
- 安裝 stylelint npm i stylelint -D
- 安裝 standard 插件 npm i stylelint-config-standard -D
- 創(chuàng)建 .stylelintrc.js 配置文件,并修改 extends 字段
module.exports = {
extends: 'stylelint-config-standard'
}
- 執(zhí)行 npx stylelint ./index.css肌厨,加上參數(shù) --fix 可以自動修復(fù)部分格式問題
- 檢查 sass 文件培慌,執(zhí)行 npm i stylelint-config-sass-guidelines -D,修改 .stylelintrc.js 文件中的 extends 為數(shù)組柑爸,添加 sass 插件
Prettier 的使用
- Prettier 幾乎可以完成所有類型文件的格式化工作
- 安裝吵护, npm i prettier -D
- 檢查某個文件并輸出檢查結(jié)果,npx prettier style.css
- 檢查并格式化某個文件竖配,npx prettier style.css --write
- 檢查并格式化項目所有文件何址,npx prettier . --write
ESLint 結(jié)合 Git Hooks
- Git Hooks
- 代碼提交至倉庫之前未執(zhí)行 lint 工作
- 使用 lint 的目的就是保證提交到倉庫的代碼是沒有問題的
- 通過 Git Hooks 在代碼提交前強制 lint
- Git Hooks 也稱為 git 鉤子,每個鉤子都對應(yīng)一個任務(wù)
- 通過 shell 腳本可以編寫鉤子任務(wù)觸發(fā)時要具體執(zhí)行的操作
- 快速上手
- 很多前端開發(fā)者并不擅長使用 shell
- Husky 可以實現(xiàn) Git Hooks 的使用需求 npm i husky -D进胯,然后在 package.json 中添加如下配置
"husky": {
"hooks": {
"pre-commit": "npm run lint"
}
}
- 配合 lint-stage 使用,npm i lint-staged -D
"lint-staged": {
"*.js*": [
"eslint",
"git add"
]
}