日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Webpack的使用——进阶篇

發布時間:2023/12/16 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Webpack的使用——进阶篇 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Webpack的使用-進階篇

目錄

  • Webpack的使用-進階篇
    • 一、create-react-app react-project
    • 二、vue create vue-project
    • 三、自定義loader
      • 3.1預備知識
      • 3.2自定義babel-loader
    • 四、自定義plugin
      • 4.1 預備知識-compiler鉤子
        • 4.1.2 tapable
        • 4.1.2 compiler鉤子
      • 4.2 預備知識-compilation鉤子
        • 4.2.1 小插曲:nodejs環境中調試
        • 4.2.2 compilation鉤子
      • 4.3 自定義CopyWebpackPlugin
    • 五、自定義Webpack
      • 5.1 Webpack 執行流程
      • 5.2 準備工作
      • 5.3 使用babel解析文件
      • 5.4 模塊化
      • 5.5 收集所有的依賴
      • 5.6 生成打包之后的bundle
    • 參考資料

主要包含以下幾部分內容:

  • React/Vue腳手架的詳細配置
  • 基于Webpack5自定義loader/plugin
  • 自己實現一個簡易的Webpack5

在學習本章之前可以先學習Webpack 的使用-基礎篇

且可配合源碼使用(⊙o⊙)… Webpack 的使用-基礎篇源碼

基礎篇主要講述如下內容:

  • Webpack 簡介
  • Webpack 初體驗
  • Webpack 開發環境的基本配置
  • Webpack 生產環境的基本配置
  • Webpack 優化配置
  • Webpack 配置詳情
  • Webpack5 使用

更詳細的Webpack配置可以查看官網Webpack官網

Webpack 的使用——進階篇源碼

一、create-react-app react-project

本部分只講述通過腳手架創建的項目的分析路線及步驟,具體每個文件夾里面講述了什么內容分別在源碼中進行注釋講解。

通過 npm run eject將配置文件暴露出來

  • config–>paths.js(向外暴露出路徑)
  • scripts–>start.js(開發環境對應的文件)
  • webpack.config.js(主要內容為對loader和plugin的配置,將來自己修改的時候可以直接在這個文件夾里面進行loader和plugin的修改)(核心)
  • scripts–>build.js(生產環境對應的文件,與開發環境對應的文件差不多)
  • 二、vue create vue-project

    這里只講述通過腳手架創建的項目的分析路線及步驟,具體每個文件夾里面講述了什么內容分別在源碼中進行注釋。

    • 通過vue inspect --mode=development > webpack.dev.js將vue開發環境配置打包一起放在webpack.dev.js文件下面,開發環境代碼只需要研究webpack.dev.js文件即可
    • 通過vue inspect --mode=production > webpack.prod.js將vue生產環境配置打包一起放在webpack.prod.js文件下面,生產環境代碼只需要研究webpack.prod.js文件即可

    開發環境文件webpack.dev.js 生產環境文件webpack.prod.js(除了在css上面以及多線程打包上面進行了一些修改,其余和開發環境是一樣的)

    三、自定義loader

    3.1預備知識

    loader本質上是一個函數

  • loader的執行順序在use數組里面是從下往上執行
  • loader里面有一個pitch方法,use數組中pitch方法的執行順序是從上往下執行,因此我們如果想先執行某些功能,可以先在pitch方法中定義
  • 同步loader
  • // 方式一 module.exports = function (content, map, meta) {console.log(111);return content; } // 方式二 module.exports = function (content, map, meta) {console.log(111);this.callback(null, content, map, meta); }module.exports.pitch = function () {console.log('pitch 111'); }
  • 異步loader
  • // 異步loader(推薦使用,loader在異步加載的過程中可以執行其余的步驟) module.exports = function (content, map, meta) {console.log(222);const callback = this.async();setTimeout(() => {callback(null, content);}, 1000) }module.exports.pitch = function () {console.log('pitch 222'); }
  • 獲取options庫:
  • 安裝loader-utils:cnpm install loader-utils 在loader中引入并使用 6. 校驗options庫: 在loader中從schema-utils引入validate并使用 創建schema.json文件校驗規則并引入使用

    loader3.js中代碼

    // 1.1 獲取options 引入 const {getOptions } = require('loader-utils'); // 2.1 獲取validate(校驗options是否合法)引入 const {validate } = require('schema-utils');// 2.3創建schema.json文件校驗規則并引入使用 const schema = require('./schema');module.exports = function(content, map, meta) {// 1.2 獲取options 使用const options = getOptions(this);console.log(333, options);// 2.2校驗options是否合法 使用validate(schema, options, {name: 'loader3'})return content; }module.exports.pitch = function() {console.log('pitch 333'); }

    schema.json中代碼

    {"type": "object","properties": {"name": {"type": "string","description": "名稱~"}},"additionalProperties": false // 如果設置為true表示除了校驗前面寫的string類型還可以 接著 校驗其余類型,如果為false表示校驗了string類型之后不可以再校驗其余類型 }

    webpack.config.js中代碼

    const path = require('path');module.exports = {module: {rules: [{test: /\.js$/,use: [{loader: 'loader3',// options部分options: {name: 'jack',age: 18}}]}]},// 配置loader解析規則:我們的loader去哪個文件夾下面尋找(這里表示的是同級目錄的loaders文件夾下面尋找)resolveLoader: {modules: ['node_modules',path.resolve(__dirname, 'loaders')]}}

    3.2自定義babel-loader

  • 創建校驗規則
  • babelSchema.json

    {"type": "object","properties": {"presets": {"type": "array"}},"addtionalProperties": true }
  • 創建loader
  • babelLoader.js

    const { getOptions } = require('loader-utils'); const { validate } = require('schema-utils'); const babel = require('@babel/core'); const util = require('util');const babelSchema = require('./babelSchema.json');// babel.transform用來編譯代碼的方法 // 是一個普通異步方法 // util.promisify將普通異步方法轉化成基于promise的異步方法 const transform = util.promisify(babel.transform);module.exports = function (content, map, meta) {// 獲取loader的options配置const options = getOptions(this) || {};// 校驗babel的options的配置validate(babelSchema, options, {name: 'Babel Loader'});// 創建異步const callback = this.async();// 使用babel編譯代碼transform(content, options).then(({code, map}) => callback(null, code, map, meta)).catch((e) => callback(e))}
  • babelLoader使用
  • webpack.config.js

    const path = require('path');module.exports = {module: {rules: [{test: /\.js$/,loader: 'babelLoader',options: {presets: ['@babel/preset-env']}}]},// 配置loader解析規則:我們的loader去哪個文件夾下面尋找(這里表示的是同級目錄的loaders文件夾下面尋找)resolveLoader: {modules: ['node_modules',path.resolve(__dirname, 'loaders')]}}

    四、自定義plugin

    4.1 預備知識-compiler鉤子

    4.1.2 tapable

    hooks tapable

  • 安裝tapable:npm install tapable -D
  • 初始化hooks容器 2.1 同步hooks,任務會依次執行:SyncHook、SyncBailHook 2.2 異步hooks,異步并行:AsyncParallelHook,異步串行:AsyncSeriesHook
  • 往hooks容器中注冊事件/添加回調函數
  • 觸發hooks
  • 啟動文件:node tapable.test.js
  • 文件tapable.test.js

    const { SyncHook, SyncBailHook, AsyncParallelHook, AsyncSeriesHook } = require('tapable');class Lesson { constructor() {// 初始化hooks容器this.hooks = {// 同步hooks,任務會依次執行// go: new SyncHook(['address'])// SyncBailHook:一旦有返回值就會退出~go: new SyncBailHook(['address']),// 異步hooks// AsyncParallelHook:異步并行// leave: new AsyncParallelHook(['name', 'age']),// AsyncSeriesHook: 異步串行leave: new AsyncSeriesHook(['name', 'age'])} } tap() {// 往hooks容器中注冊事件/添加回調函數this.hooks.go.tap('class0318', (address) => {console.log('class0318', address);return 111;})this.hooks.go.tap('class0410', (address) => {console.log('class0410', address);})// tapAsync常用,有回調函數this.hooks.leave.tapAsync('class0510', (name, age, cb) => {setTimeout(() => {console.log('class0510', name, age);cb();}, 2000)})// 需要返回promisethis.hooks.leave.tapPromise('class0610', (name, age) => {return new Promise((resolve) => {setTimeout(() => {console.log('class0610', name, age);resolve();}, 1000)})}) }start() {// 觸發hooksthis.hooks.go.call('c318');this.hooks.leave.callAsync('jack', 18, function () {// 代表所有leave容器中的函數觸發完了,才觸發console.log('end~~~');}); } }const l = new Lesson(); l.tap(); l.start();

    4.1.2 compiler鉤子

  • 工作方式:異步串行執行,因此下面代碼輸出順序如下: 1.1 emit.tap 111 1.2 1秒后輸出 emit.tapAsync 111 1.3 1秒后輸出 emit.tapPromise 111 1.4 afterEmit.tap 111 1.5 done.tap 111
  • tapAsync和tapPromise表示異步
  • 這邊只簡單介紹了幾個complier,具體開發的過程中可以根據文檔介紹編寫(很方便的)
  • class Plugin1 {apply(complier) {complier.hooks.emit.tap('Plugin1', (compilation) => {console.log('emit.tap 111');})complier.hooks.emit.tapAsync('Plugin1', (compilation, cb) => {setTimeout(() => {console.log('emit.tapAsync 111');cb();}, 1000)})complier.hooks.emit.tapPromise('Plugin1', (compilation) => {return new Promise((resolve) => {setTimeout(() => {console.log('emit.tapPromise 111');resolve();}, 1000)})})complier.hooks.afterEmit.tap('Plugin1', (compilation) => {console.log('afterEmit.tap 111');})complier.hooks.done.tap('Plugin1', (stats) => {console.log('done.tap 111');})} }module.exports = Plugin1;

    4.2 預備知識-compilation鉤子

    4.2.1 小插曲:nodejs環境中調試

  • package.json中輸入(–inspect-brk 表示通過斷點的方式調試,,,,,,./node_modules/webpack/bin/webpack.js" 表示調試這個文件,,,,,,node 表示通過node運行)
  • "scripts": {"start": "node --inspect-brk ./node_modules/webpack/bin/webpack.js"}
  • 在需要調試的地方打一個debugger
  • 通過node運行文件
  • 在一個網站中右擊檢查,點擊綠色圖標
  • 便可以調試了,和在網頁中調試代碼一樣的

    4.2.2 compilation鉤子

  • 初始化compilation鉤子
  • 往要輸出資源中,添加一個a.txt文件
  • 讀取b.txt中的內容,將b.txt中的內容添加到輸出資源中的b.txt文件中 3.1 讀取b.txt中的內容需要使用node的readFile模塊 3.2 將b.txt中的內容添加到輸出資源中的b.txt文件中除了使用 2 中的方法外,還有兩種形式可以使用 3.2.1 借助RawSource 3.2.2 借助RawSource和emitAsset
  • const fs = require('fs'); const util = require('util'); const path = require('path');const webpack = require('webpack'); const { RawSource } = webpack.sources;// 將fs.readFile方法變成基于promise風格的異步方法 const readFile = util.promisify(fs.readFile);/*1. 初始化compilation鉤子2. 往要輸出資源中,添加一個a.txt文件3. 讀取b.txt中的內容,將b.txt中的內容添加到輸出資源中的b.txt文件中3.1 讀取b.txt中的內容需要使用node的readFile模塊3.2 將b.txt中的內容添加到輸出資源中的b.txt文件中除了使用 2 中的方法外,還有兩種形式可以使用3.2.1 借助RawSource3.2.2 借助RawSource和emitAsset */class Plugin2 {apply(compiler) {// 1.初始化compilation鉤子compiler.hooks.thisCompilation.tap('Plugin2', (compilation) => {// debugger// console.log(compilation);// 添加資源compilation.hooks.additionalAssets.tapAsync('Plugin2', async (cb) => {// debugger// console.log(compilation);const content = 'hello plugin2';// 2.往要輸出資源中,添加一個a.txtcompilation.assets['a.txt'] = {// 文件大小size() {return content.length;},// 文件內容source() {return content;}}const data = await readFile(path.resolve(__dirname, 'b.txt'));// 3.2.1 compilation.assets['b.txt'] = new RawSource(data);// 3.2.1compilation.emitAsset('b.txt', new RawSource(data));cb();})})}}module.exports = Plugin2;

    4.3 自定義CopyWebpackPlugin

    CopyWebpackPlugin的功能:將public文件夾中的文件復制到dist文件夾下面(忽略index.html文件)

  • 創建schema.json校驗文件
  • {"type": "object","properties": {"from": {"type": "string"},"to": {"type": "string"},"ignore": {"type": "array"}},"additionalProperties": false }
  • 創建CopyWebpackPlugin.js插件文件
  • 編碼思路 下載schema-utils和globby:npm install globby schema-utils -D 將from中的資源復制到to中,輸出出去 1. 過濾掉ignore的文件 2. 讀取paths中所有資源 3. 生成webpack格式的資源 4. 添加compilation中,輸出出去

    const path = require('path'); const fs = require('fs'); const {promisify} = require('util')const { validate } = require('schema-utils'); const globby = require('globby');// globby用來匹配文件目標 const webpack = require('webpack');const schema = require('./schema.json'); const { Compilation } = require('webpack');const readFile = promisify(fs.readFile); const {RawSource} = webpack.sourcesclass CopyWebpackPlugin {constructor(options = {}) {// 驗證options是否符合規范validate(schema, options, {name: 'CopyWebpackPlugin'})this.options = options;}apply(compiler) {// 初始化compilationcompiler.hooks.thisCompilation.tap('CopyWebpackPlugin', (compilation) => {// 添加資源的hookscompilation.hooks.additionalAssets.tapAsync('CopyWebpackPlugin', async (cb) => {// 將from中的資源復制到to中,輸出出去const { from, ignore } = this.options;const to = this.options.to ? this.options.to : '.';// context就是webpack配置// 運行指令的目錄const context = compiler.options.context; // process.cwd()// 將輸入路徑變成絕對路徑const absoluteFrom = path.isAbsolute(from) ? from : path.resolve(context, from);// 1. 過濾掉ignore的文件// globby(要處理的文件夾,options)const paths = await globby(absoluteFrom, { ignore });console.log(paths); // 所有要加載的文件路徑數組// 2. 讀取paths中所有資源const files = await Promise.all(paths.map(async (absolutePath) => {// 讀取文件const data = await readFile(absolutePath);// basename得到最后的文件名稱const relativePath = path.basename(absolutePath);// 和to屬性結合// 沒有to --> reset.css// 有to --> css/reset.css(對應webpack.config.js中CopyWebpackPlugin插件的to的名稱css)const filename = path.join(to, relativePath);return {// 文件數據data,// 文件名稱filename}}))// 3. 生成webpack格式的資源const assets = files.map((file) => {const source = new RawSource(file.data);return {source,filename: file.filename}})// 4. 添加compilation中,輸出出去assets.forEach((asset) => {compilation.emitAsset(asset.filename, asset.source);})cb();})})}}module.exports = CopyWebpackPlugin;
  • 在webpack.config.js中使用
  • 五、自定義Webpack

    5.1 Webpack 執行流程

  • 初始化 Compiler:webpack(config) 得到 Compiler 對象
  • 開始編譯:調用 Compiler 對象 run 方法開始執行編譯
  • 確定入口:根據配置中的 entry 找出所有的入口文件。
  • 編譯模塊:從入口文件出發,調用所有配置的 Loader 對模塊進行編譯,再找出該模塊依賴的模塊,遞歸直到所有模塊被加載進來
  • 完成模塊編譯: 在經過第 4 步使用 Loader 編譯完所有模塊后,得到了每個模塊被編譯后的最終內容以及它們之間的依賴關系。
  • 輸出資源:根據入口和模塊之間的依賴關系,組裝成一個個包含多個模塊的 Chunk,再把每個 Chunk 轉換成一個單獨的文件加入到輸出列表。(注意:這步是可以修改輸出內容的最后機會)
  • 輸出完成:在確定好輸出內容后,根據配置確定輸出的路徑和文件名,把文件內容寫入到文件系統
  • 5.2 準備工作

  • 創建文件夾myWebpack
  • 創建src–>(add.js / count.js / index.js),寫入對應的js代碼
  • 創建config–>webpack.config.js寫入webpack基礎配置(entry和output)
  • 創建lib文件夾,里面寫webpack的主要配置
  • 創建script–>build.js(將lib文件夾下面的myWebpack核心代碼和config文件下的webpack基礎配置引入并調用run()函數開始打包)
  • 為了方便啟動,控制臺通過輸入命令 npm init -y拉取出package.json文件,修改文件中scripts部分為"build": "node ./script/build.js"表示通過在終端輸入命令npm run build時會運行/script/build.js文件,在scripts中添加"debug": "node --inspect-brk ./script/build.js"表示通過在終端輸入命令npm run debug時會調試/script/build.js文件中的代碼,調試代碼的步驟第四章已經介紹
  • 5.3 使用babel解析文件

  • 創建文件lib–>myWebpack1–>index.js
  • 下載三個babel包 babel官網
  • npm install @babel/parser -D用來將代碼解析成ast抽象語法樹 npm install @babel/traverse -D用來遍歷ast抽象語法樹代碼 npm install @babel/core-D用來將代碼中瀏覽器不能識別的語法進行編譯 3. 編碼思路 1. 讀取入口文件內容 2. 將其解析成ast抽象語法樹 3. 收集依賴 4. 編譯代碼:將代碼中瀏覽器不能識別的語法進行編譯

    index.js

    const fs = require('fs'); const path = require('path');// babel的庫 const babelParser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const { transformFromAst } = require('@babel/core');function myWebpack(config) {return new Compiler(config); }class Compiler {constructor(options = {}) {this.options = options;}// 啟動webpack打包run() {// 1. 讀取入口文件內容// 入口文件路徑const filePath = this.options.entry;const file = fs.readFileSync(filePath, 'utf-8');// 2. 將其解析成ast抽象語法樹const ast = babelParser.parse(file, {sourceType: 'module' // 解析文件的模塊化方案是 ES Module})// debugger;console.log(ast);// 獲取到文件文件夾路徑const dirname = path.dirname(filePath);// 定義存儲依賴的容器const deps = {}// 3. 收集依賴traverse(ast, {// 內部會遍歷ast中program.body,判斷里面語句類型// 如果 type:ImportDeclaration 就會觸發當前函數ImportDeclaration({node}) {// 文件相對路徑:'./add.js'const relativePath = node.source.value;// 生成基于入口文件的絕對路徑const absolutePath = path.resolve(dirname, relativePath);// 添加依賴deps[relativePath] = absolutePath;}})console.log(deps);// 4. 編譯代碼:將代碼中瀏覽器不能識別的語法進行編譯const { code } = transformFromAst(ast, null, {presets: ['@babel/preset-env']})console.log(code);} }module.exports = myWebpack;

    5.4 模塊化

    我們開發代碼過程中講究的是模塊化開發,不同功能的代碼放在不同的文件中 創建myWebpack2–>parser.js(放入解析代碼)/Compiler.js(放入編譯代碼)/index.js(主文件)

    5.5 收集所有的依賴

    所有代碼位于myWebpack文件夾中 Compiler.js文件中build函數用于構建代碼,run函數中modules通過遞歸遍歷收集所有的依賴,depsGraph用于將依賴整理更好依賴關系圖(具體的代碼功能都在代碼中進行了注釋)

    5.6 生成打包之后的bundle

    代碼位于myWebpack–>Compiler.js中的bundle部分 整個myWebpack–>Compiler.js代碼

    const path = require('path'); const fs = require('fs'); const {getAst,getDeps,getCode } = require('./parser')class Compiler {constructor(options = {}) {// webpack配置對象this.options = options;// 所有依賴的容器this.modules = [];}// 啟動webpack打包run() {// 入口文件路徑const filePath = this.options.entry;// 第一次構建,得到入口文件的信息const fileInfo = this.build(filePath);this.modules.push(fileInfo);// 遍歷所有的依賴this.modules.forEach((fileInfo) => {/**{'./add.js': '/Users/xiongjian/Desktop/atguigu/code/05.myWebpack/src/add.js','./count.js': '/Users/xiongjian/Desktop/atguigu/code/05.myWebpack/src/count.js'} */// 取出當前文件的所有依賴const deps = fileInfo.deps;// 遍歷for (const relativePath in deps) {// 依賴文件的絕對路徑const absolutePath = deps[relativePath];// 對依賴文件進行處理const fileInfo = this.build(absolutePath);// 將處理后的結果添加modules中,后面遍歷就會遍歷它了~(遞歸遍歷)this.modules.push(fileInfo);}})console.log(this.modules);// 將依賴整理更好依賴關系圖/*{'index.js': {code: 'xxx',deps: { 'add.js': "xxx" }},'add.js': {code: 'xxx',deps: {}}}*/const depsGraph = this.modules.reduce((graph, module) => {return {...graph,[module.filePath]: {code: module.code,deps: module.deps}}}, {})console.log(depsGraph);this.generate(depsGraph)}// 開始構建build(filePath) {// 1. 將文件解析成astconst ast = getAst(filePath);// 2. 獲取ast中所有的依賴const deps = getDeps(ast, filePath);// 3. 將ast解析成codeconst code = getCode(ast);return {// 文件路徑filePath,// 當前文件的所有依賴deps,// 當前文件解析后的代碼code}}// 生成輸出資源generate(depsGraph) {/* index.js的代碼"use strict";\n' +'\n' +'var _add = _interopRequireDefault(require("./add.js"));\n' +'\n' +'var _count = _interopRequireDefault(require("./count.js"));\n' +'\n' +'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +'\n' +'console.log((0, _add["default"])(1, 2));\n' +'console.log((0, _count["default"])(3, 1));*/const bundle = `(function (depsGraph) {// require目的:為了加載入口文件function require(module) {// 定義模塊內部的require函數function localRequire(relativePath) {// 為了找到要引入模塊的絕對路徑,通過require加載return require(depsGraph[module].deps[relativePath]);}// 定義暴露對象(將來我們模塊要暴露的內容)var exports = {};(function (require, exports, code) {eval(code);})(localRequire, exports, depsGraph[module].code);// 作為require函數的返回值返回出去// 后面的require函數能得到暴露的內容return exports;}// 加載入口文件require('${this.options.entry}');})(${JSON.stringify(depsGraph)})`// 生成輸出文件的絕對路徑const filePath = path.resolve(this.options.output.path, this.options.output.filename)// 寫入文件fs.writeFileSync(filePath, bundle, 'utf-8');} }module.exports = Compiler;

    參考資料

    1: 感謝熊健老師的視頻講解!

    2:注釋很清楚的一篇關于webpack基礎的博客

    總結

    以上是生活随笔為你收集整理的Webpack的使用——进阶篇的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。