[源碼-webpack03] 手寫webpack - compiler簡單編譯流程

導航

[深刻01] 執行上下文
[深刻02] 原型鏈
[深刻03] 繼承
[深刻04] 事件循環
[深刻05] 柯里化 偏函數 函數記憶
[深刻06] 隱式轉換 和 運算符
[深刻07] 瀏覽器緩存機制(http緩存機制)
[深刻08] 前端安全
[深刻09] 深淺拷貝
[深刻10] Debounce Throttle
[深刻11] 前端路由
[深刻12] 前端模塊化
[深刻13] 觀察者模式 發佈訂閱模式 雙向數據綁定
[深刻14] canvas
[深刻15] webSocket
[深刻16] webpack
[深刻17] http 和 https
[深刻18] CSS-interview
[react] Hookscss

[部署01] Nginx
[部署02] Docker 部署vue項目
[部署03] gitlab-CIhtml

[源碼-webpack01-前置知識] AST抽象語法樹
[源碼-webpack02-前置知識] Tapable
[源碼-webpack03] 手寫webpack - compiler簡單編譯流程前端

前置知識

一些單詞

compiler:編譯
複製代碼

npm link

  • (1) 先把須要link的包在根目錄執行:---------------------------------- npm link
    • 經過 npm link 能夠把包link到全局
    • 該包須要有 bin/wpack.js
    • 在package.json中設置 bin: { wpack: '路徑'}
  • (2) 在須要使用該包的項目中的根目錄,執行命令:------------------- npm link wpack
    • 則會把wpack包安裝到node_modules中
  • (3) 驗證
    • 在使用到wpack包的項目中,執行命令:------------------------- npx wpack

process.cwd() ------------------------ 當前工做目錄

  • process.cwd() 返回 Node.js 進程的當前工做目錄
  • process.cwd()

fs.readFileSync(path[, options]) ----- 讀文件

  • 做用:返回path的內容
  • 參數:
    • path:文件名或文件描述符
    • options: 配置項,object|string
      • encoding:編碼格式,可選

path.relative(from, to) --------------- from 到 to 的行對路徑

  • path.relative() 方法根據當前工做目錄返回 ( from ) 到 ( to ) 的 ( 相對路徑 )

path.dirname(path) ------------------ 最後一段的父目錄

  • path.dirname() 方法返回 path 的目錄名
  • 即 ( 返回路徑中最後一段文件或者文件夾所在的文件夾,即最後一段文件或文件夾的父目錄 )

path.extname(path) ------------------ 返回path的擴展名

  • path.extname(path) 返回path的擴展名
  • ext是 extend:擴展

arguments.callee --------------------- 指向當前執行的函數 (嚴格模式下禁止)

  • arguments.callee --------------------- 指向當前執行的函數 (嚴格模式下禁止)

AST explorer

源碼:
require('./a.js')



AST:
{
  "type": "Program",
  "start": 0,
  "end": 17,
  "body": [
    { // --------------------------------------- body數組可能包含多個statement狀態對象
      "type": "ExpressionStatement", 
      "start": 0,
      "end": 17,
      "expression": {
        "type": "CallExpression", // ----------- 調用表達式
        "start": 0,
        "end": 17,
        "callee": { // ------------------------- callee.name = 'require'
          "type": "Identifier",
          "start": 0,
          "end": 7,
          "name": "require"
        },
        "arguments": [ // ---------------------- 參數列表
          {
            "type": "Literal",
            "start": 8,
            "end": 16,
            "value": "./a.js",
            "raw": "'./a.js'"
          }
        ]
      }
    }
  ],
  "sourceType": "module"
}
複製代碼

babel相關的AST插件

  • @babel/core
    • 核心文件
  • @babel/parse
    • 將源碼string轉成AST
  • @babe/traverse
    • 遍歷AST
    • enter(path)進入exit(path)退出 等鉤子
  • @babel/types
    • 修改,添加,刪除等,操做AST
    • 用於 AST 的類 lodash 庫,其封裝了大量與 AST 有關的方法,大大下降了轉換 AST 的成本
    • babelTypes.stringLiteral(modulePath)
  • @bebe/generator
    • 將修改後的AST轉換成源碼string

const options = loaderUtils.getOptions(this)vue

Loader - 編寫一個自定義loader

  • ( loader ) 是一個 ( 函數 ),函數的第一個參數表示 ( 該loader匹配的文件的 源代碼 )node

  • loader 不能寫成 ( 箭頭函數 ),由於須要經過this獲取更多的apireact

  • loader-utilswebpack

    • 用來獲取 module -> rules 中的 loader的 ( options ) 對象
    • 經過 ( loader-utils ) 中的 ( getOptions ) 來獲取 ( options ) 對象
    • 安裝: npm install loader-utils -D
    • 使用:const options = loaderUtils.getOptions(this)
    • loader-utils
  • this.callbackgit

    • 第一個參數:err // Error 或者 null
    • 第二個參數:content // string或者buffer,即處理事後的源代碼
    • 第三個參數:sourceMap? // 可選,必須是一個能夠被這個模塊解析的 source map
    • 第四個參數:meta? //可選,即元數據
    • this.callback - webpack官網文檔
  • this.asyncgithub

    • this.async 主要用於處理loader中的異步操做
    • 返回值是: this.callback()
  • 編寫好的loader,如何在webpack.config.js中引入?web

    • 在根目錄中新建 loaders 文件夾,裏面存放 replace-loader.js
    • 單個loader
    module.exports = {
        ...
        module: {
            rules: [{
                test: /\.js$/,
                use: [{
                    loader: path.resolve(__dirname, 'loaders/replace-loader'), // 須要用到path模塊
                    options: {
                        name: 'aaaaa'
                    }
                }]
            }]
        }
    }
    複製代碼
    • 多個loader
    module.exports = {
        ...
        resolveLoader: { // resolveLoader配置項
            modules: ['node_modules', path.resolve(__dirname, 'loaders')]
            // 告訴 webpack 該去那個目錄下找 loader 模塊
            // 先從node_modules中尋找,再在loaders文件夾中尋找
            // modules: ['node_modules', './loaders/']
        },
        module: {
            rules: [{
                test: /\.js$/,
                use: [{
                    loader: 'upper-loader', 
                    options: {
                        name: 'aaaaa'
                    }
                },{
                    loader: 'replace-loader',
                    options: {
                        name: 'hi!!!!???&&&&'
                    }
                    // 直接加載在loaders文件夾中的 replace-loader.js,這裏只須要寫上loader的名字便可
                }]
            }]
        }
    }
    複製代碼
  • 自定義loader實例

    • loaders/replace-loader.js
    const loaderUtils = require('loader-utils')
    // loader-utils插件
    // 能夠經過loader-utils中的getOptions拿到loader中的options對象
    
    module.exports = function(source) {
      // source就是該loader匹配的文件的源碼
      
      const options = loaderUtils.getOptions(this)
      // 經過 loader-utils的getOptions獲取options對象
    
      const callback = this.async()
      // this.async()用來處理loader中的異步操做, -------- 返回值是:this.callback()
      // this.callback(err, content, sourceMap?, meta?)
      
      setTimeout(function() {
        const result = source.replace('hello', options.name)
        callback(null, result)
      }, 1000)
    }
    複製代碼
    • webpack.config.js
    const path = require('path')
    
    module.exports = {
      mode: 'development',
      entry: {
        index: './src/index.js'
      },
      output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js'
      },
      module: {
        rules: [
          // {
          //   test: /\.js$/,
          //   use: [{
          //     loader: path.resolve(__dirname, 'loaders/replace-loader.js'),
          //     options: {
          //       name: 'woow_wu7'
          //     }
          //   }]
          // }
          {
            test: /\.js$/,
            use: [{
              loader: 'replace-loader', // 這裏的名字就是 loaders 文件夾中的 replace-loader.js 文件名
              options: {
                name: 'woow_wu77'
              }
            }]
          }
        ]
      },
      resolveLoader: { 
        // 規定加載loader的地方限制在 node_modules 文件夾中,和 './loaders/'文件夾中
        // 先找 node_modules 再找 './loaders/'
        modules: ['node_modules', './loaders/']
      }
    }
    複製代碼

Plugin - 編寫一個自定義plugin

  • plugin是一個具備 ( apply ) 方法的對象,apply方法會被 ( compiler) 調用,而且 compiler 對象可在整個編譯生命週期訪問

webpack打包後文件分析

  • 精簡代碼,去除 webpack_require 上的無關屬性,代碼以下

  • 再繼續簡化
(function(modules){
    var initialMoudles = {}
    
    function __webpack_require__(moduleId)
    return __webpack_require__('./src/index.js')
})()

自執行後,至關於調用 __webpack_require__('./src/index.js'),而且 initialMoudles 成爲閉包變量,常駐內存
複製代碼
  • 參數對象 modules
{
    "./src/a.js": function () { eval("") },
    "./src/base/b.js": function () { eval("") },
    "./src/base/c.js": function () { eval("") },
    "./src/index.js": function () { eval("") },
}
複製代碼
  • 第一步:
    • 調用 __webpack_require__('./src/index.js')
    • 執行 modules[moduleId].call() 即執行modules參數對象'./src/index'中的eval()源碼
  • 第二步
    • 調用 __webpack_require__('./src/a.js')
    • 執行 modules[moduleId].call() 即執行modules參數對象'./src/a.js'中的eval()源碼
  • 第三步
    • 調用 __webpack_require__('./src/b.js')
    • 執行 modules[moduleId].call() 即執行modules參數對象'./src/b.js'中的eval()源碼
  • 第四步
    • 調用 __webpack_require__('./src/c.js')
    • 執行 modules[moduleId].call() 即執行modules參數對象'./src/c.js'中的eval()源碼
  • 直到modules中的全部moudleId對應的源碼都執行完

手寫webpack - compiler

流程

  • buildModul() - modules對象的賦值過程
    • (1) 將webpack.config.js做爲參數傳入Compiler類
    • (2) 經過new命令調用Compiler,生成compiler實例,並調用Compiler.prototype上的 run 方法
    • (3) 在 run 方法中調用 buildModule() 和 emitFile()
    • (4) buildModule() 方法接受webpack.config.js中的 ( 入口文件的絕對路徑 ) 和 ( 是不是主模塊 ) 爲參數
    • (5) 在 buildModule() 中調用 getSource('absolutePath') 方法
      • 參數是模塊的絕對路徑
      • 經過 fs.readFileSync(path, options) 讀取源碼
      • 循環webpack.config.js中的module->rules數組->test,用test和absolutePath作正則匹配,匹配成功的話,就遞歸調用loader函數解析該文件,並返回該文件,直到moudle->rules->use中的數組成員loader都調用完
    • (6) 在 buildModule() 中調用 parse() 方法解析源碼,修改源碼,返回源碼
    • (7) parse()方法
      • 參數有兩個:模塊的源碼 和 模塊文件所在的文件夾路徑
      • 返回值有兩個:修改後的模塊源碼 和 該模塊的依賴數組
      • 注意:修改部分(替換require名,moudules中的key要是'./src/xxxxx'的格式,匹配loader並處理源文件)
    • (8) 將 模塊的相對路徑 和 模塊修改後的源碼 一一對應做爲 modules對象的 key和value值
    • (9) 若是 parse()返回的該模塊的依賴數組不爲空,則遍歷該模塊的依賴數組,並遞歸調用 buildModule 方法,直到最後一個模塊沒有依賴爲止
  • emitFile() - 將源碼發射到webpack.config.js指定的目錄的過程
    • (1) 安裝ejs模板引擎 並編寫模板 傳入兩個參數 entryId 和 modules
    • (2) 獲取webpack.config.js中的output對象的path,filename
    • (3) fs.readFileSync()讀取ejs模板源文件
    • (4) 將esj.render() 生成能夠執行的文件
    • (5) fs.writeFileSync(file, data[, options])將生成的經esj編譯後的源文件寫入output.path中,文件名是outpt.name
wpack.js

#! /usr/bin/env node

// 一.須要拿到 webpack.config.js 文件

const path = require('path')
const config = require(path.resolve('webpack.config.js')) // 獲取webpack.config.js
const Compiler = require('../lib/compiler.js')


const compiler = new Compiler(config)

compiler.run() // 調用run方法,
複製代碼

Compiler - run()方法

  • ( run ) 方法主要作 ( 兩件 ) 事情
    • (1) 調用buildModul() -> modules = { } ------------- 依賴關係對象的key和vlue的收集
      • key:全部模塊的相對路徑
      • value:全部模塊的源碼
    • (2) 調用emitFile方法 -> 發射打包後的文件到指定的文件夾中
  • 具體流程
    • 在run中調用 ( buildModule ) 方法
    • 在run中調用 ( emitFile ) 方法

Compiler - run() - buildMoudle()方法

  • buildMoudle(moduleAbsolutePath, isEntryModule)
  • buildMoudle()參數
    • moduleAbsolutePath:每一個模塊的絕對路徑
    • isEntryModule:布爾值,是不是入口主模塊,入口模塊通常是index.js
  • buildmoudle主要作如下幾件事情
    • 經過 fs.readFileSync(modulePath, { encoding: 'utf8' }) 讀取傳入的模塊路徑對應的源碼
    • 若是是 ( 主入口模塊 ),就用 ( this.entryId ) 來標記主入口模塊的路徑 (路徑須要處理成想要的格式)
    • 調用 parse() 方法
      • 傳入:( 未修改的源碼 ) 和 入口文件所在 ( 文件夾 )
      • 返回:( 修改事後的源碼 ) 和 當前模塊的依賴數組,即 ( 當前模塊require的文件 )
        • 修改源碼
          • 經過 @babel/parser 將源碼轉成AST
          • 經過 @babel/traverse 遍歷AST,並在遍歷過程當中經過 @babel/types完成修改,添加,刪除等操做
          • 經過 @babel/types 修改,添加,刪除AST的各個節點
          • 經過 @babel/generator將修改後的AST轉成源碼字符串
    • 若是 ( 當前模塊還有依賴項 ),即返回的當前模塊的依賴項數組不爲空,就 ( 遞歸執行buildMoudle() ) 方法
    • 最終收集全部的模塊對應關係到 modules對象中 this.modules[moduleRelativePath] = sourceCode
buildModule(moduleAbsolutePath, isEntry) {
    // 參數
    // moduleAbsolutePath:是模塊的絕對路徑,經過path.resolve(this.root, this.entry)得到
    // isEntry:是不是入口主模塊


    const source = this.getSource(moduleAbsolutePath)
    // 讀取模塊的源文件內容

    const moduleRelativePath = './' + path.relative(this.root, moduleAbsolutePath)
    // path.relative(from, to) 
    // path.relative(from, to)方法根據當前工做目錄返回 from 到 to 的相對路徑
    // moduleRelativePath
    // 表示模塊文件的相對路徑
    // moduleRelativePath = moduleAbsolutePath - this.root

    // console.log(source, moduleRelativePath)

    if (isEntry) {
      this.entryId = moduleRelativePath
      // 若是是主入口,把改造後的形如 ./src/index.js 的文件路徑賦值給 entryId
    }

    const fatherPath = path.dirname(moduleRelativePath)
    // fatherPath 即獲取 ./src/index.js 的最後一段文件或文件夾的父目錄 => ./src


    const {sourceCode, dependencies} = this.parse(source, fatherPath).replace(/\\/g, '/');
    // parse()主要功能
      // 1. 對入口文件源碼進行改造
      // 2. 返回改造後的源碼 和 依賴列表
    // 參數:
      // 改造前的源碼
      // 和父路徑
    // 返回值
      // 改造後的源碼
      // 依賴列表


    this.modules[moduleRelativePath] = sourceCode;
    // this.modules
      // 模塊的路徑 和 模塊的源碼一一對應
      // key   => moduleRelativePath
      // value => sourceCode

      dependencies.forEach(dep => { // 附模塊的加載 遞歸加載
        this.buildModule(path.join(this.root, dep), false)
      })
      // 遞歸依賴數組,將this.modules對象的全部key,vaue收起到一塊兒

  }
複製代碼

Compiler - run() - buildMoudle() - getSource()方法 - 增長loader解析源碼後再給到parse()去轉換

  • less-loader
const less = require('less')
const lessLoader = function(source) {
  const that = this;
  let res;
  less.render(source, function(err, content) {
    res = content.css.replace(/\n/g, '\\n').replace(/\r/g, '\\r')
    // res = that.callback(null, content.css.replace(/\n/g, '\\n'))
  })
  return res;
}
module.exports = lessLoader
複製代碼
  • style-loader
const styleLoader = function(source) {
  const style = `
    const styleElement = document.createElement('style');
    styleElement.innerHTML = ${JSON.stringify(source)};
    document.head.appendChild(styleElement);
  `
  return style
}

module.exports = styleLoader
複製代碼
  • getSource()方法中添加loader部分的代碼
getSource()方法中添加loader部分的代碼


  getSource(modulePath) {
    let content = fs.readFileSync(modulePath, { encoding: 'utf8' })
    const { rules } = this.config.module // 獲取rule數組
    for(let i = 0; i < rules.length; i++) { // 循環rules數組
      const {test, use} = rules[i] // 取出每一個對象中的test和use
      let reverseIndex = use.length - 1; // use也是一個數組,從後往前,從下往上執行
      if (test.test(modulePath)) {
        function runLoader() {
          const loader = require(use[reverseIndex--]) 
          // 先去use數組中的最後一個,再一次取前一個
          // require('absolute path') 引入loader函數
          
          content = loader(content) 
          // 執行loader函數,返回loader修改後的內容
          
          if (reverseIndex >= 0) { // 循環遞歸結束條件
            runLoader()
          }
        }
        runLoader()
      }
    }
    // content
    // fs.readFileSync(modulePath, {encoding: 'utf8'}) 讀取模塊源碼,返滬utf8格式的源碼
    // 參數:
    // modulePath:這裏是模塊的 絕對路徑
    return content
  }
複製代碼

Compiler - run() - buildMoudle() - parse()方法

parse(source, parentPath) { // AST (解析 -> 遍歷 -> 轉換 -> 生成)
    const dependencies = [] //  依賴數組

    // 解析
    const AST = babelParser.parse(source)

    // 遍歷
    babelTraverse(AST, {
      CallExpression(p) { // 調用表達式,注意這裏參數不能寫成path,和node的path衝突了
        // 修改
          // 主要作兩件事情
          // 1. require() => __webpack_require__()
          // 2. require('./a.js') => require('./src/a.js) const node = p.node if (node.callee.name === 'require') { // 找到節點中的callee.name是require的方法,修更名字 node.callee.name = '__webpack_require__' // 替換require的名字 let modulePath = node.arguments[0].value; modulePath = "./" + path.join(parentPath, modulePath).replace(/\\/g, '/') + (path.extname(modulePath) ? '' : '.js'); // 後綴存在就加空字符串即不作操做,不存在加.js // 例如:modulePath = './' + '/src' + 'index' + '.js' // 獲取require的參數 dependencies.push(modulePath) // 轉換 node.arguments = [babelTypes.stringLiteral(modulePath)] // 把AST中的argumtns中的Literal修改掉 => 修改爲最新的modulePath } } }) // 生成 const sourceCode = babelGenerator(AST).code; // 返回 return {sourceCode, dependencies} } 複製代碼

Compiler - run() - emitFile()

emitFile() { // 發射文件
    console.log(111111111)
    const {path: p, filename} = this.config.output

    const main = path.join(p, filename)
    // main 表示打包後的文件的路徑

    const templeteSourceStr = this.getSource(path.join(__dirname, 'main.ejs'))
    // 讀取模塊源文件 main.ejs

    const code = ejs.render(templeteSourceStr, {
      entryId: this.entryId,
      modules: this.modules
    })
    // 渲染模板
    // 模板中有兩個參數 entryId 和 modules

    this.assets = {}
    this.assets[main] = code;
    // key:打包後的文件路徑
    // value: 打包後的文件源碼

    fs.writeFileSync(main, this.assets[main])
    // 寫文件按
    // fs.writeFileSync(file, data[, options])
  }

複製代碼
------
main.ejs


(function (modules) { 
  var installedModules = {};

  function __webpack_require__(moduleId) {

    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    var module = installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}
    };

    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

    module.l = true;

    return module.exports;
  }

  return __webpack_require__(__webpack_require__.s = "<%-entryId%>");
})
  ({
    <%for(let key in modules){%>
      "<%-key%>":
      (function (module, exports, __webpack_require__) {
        eval(`<%-modules[key]%>`)
      }),
    <%}%>
  });
複製代碼

Compiler 總文件

const fs = require('fs')
const path = require('path')
const babelParser = require('@babel/parser')
const babelTypes = require('@babel/types')
const babelTraverse = require('@babel/traverse').default
const babelGenerator = require('@babel/generator').default
const ejs = require('ejs')

class Compiler {
  constructor(config) {
    this.config = config // webapck.config.js中的內容,即webpack配置文件模塊

    this.entryId = null // 入口文件的相對路徑

    this.modules = {}
    // 用來保存全部模塊信息
    // key:模塊的相對路徑
    // value:模塊的源碼

    this.entry = config.entry.index; // 入口文件路徑
    this.root = process.cwd(); // 當前工做路徑,返回node.js進程的當前工做目錄
  }

  getSource(modulePath) {
    const content = fs.readFileSync(modulePath, { encoding: 'utf8' })
    // content
    // fs.readFileSync(modulePath, {encoding: 'utf8'}) 讀取模塊源碼,返滬utf8格式的源碼
    // 參數:
    // modulePath:這裏是模塊的 絕對路徑
    return content
  }

  parse(source, parentPath) { // AST (解析 -> 遍歷 -> 轉換 -> 生成)
    const dependencies = [] //  依賴數組

    // 解析
    const AST = babelParser.parse(source)

    // 遍歷
    babelTraverse(AST, {
      CallExpression(p) { // 調用表達式,注意這裏參數不能寫成path,和node的path衝突了
        // 修改
          // 主要作兩件事情
          // 1. require() => __webpack_require__()
          // 2. require('./a.js') => require('./src/a.js) const node = p.node if (node.callee.name === 'require') { // 找到節點中的callee.name是require的方法,修更名字 node.callee.name = '__webpack_require__' // 替換require的名字 let modulePath = node.arguments[0].value; modulePath = "./" + path.join(parentPath, modulePath).replace(/\\/g, '/') + (path.extname(modulePath) ? '' : '.js'); // 後綴存在就加空字符串即不作操做,不存在加.js // 例如:modulePath = './' + '/src' + 'index' + '.js' // 獲取require的參數 dependencies.push(modulePath) // 轉換 node.arguments = [babelTypes.stringLiteral(modulePath)] // 把AST中的argumtns中的Literal修改掉 => 修改爲最新的modulePath } } }) // 生成 const sourceCode = babelGenerator(AST).code; // 返回 return {sourceCode, dependencies} } buildModule(moduleAbsolutePath, isEntry) { // 參數 // moduleAbsolutePath:是模塊的絕對路徑,經過path.resolve(this.root, this.entry)得到 // isEntry:是不是入口主模塊 const source = this.getSource(moduleAbsolutePath) // 讀取模塊的源文件內容 let moduleRelativePath = './' + path.relative(this.root, moduleAbsolutePath).replace(/\\/g, '/'); console.log(path.relative(this.root, moduleAbsolutePath)) // path.relative(from, to) // path.relative(from, to)方法根據當前工做目錄返回 from 到 to 的相對路徑 // moduleRelativePath // 表示模塊文件的相對路徑 // moduleRelativePath = moduleAbsolutePath - this.root // console.log(source, moduleRelativePath) if (isEntry) { this.entryId = moduleRelativePath // 若是是主入口,把改造後的形如 ./src/index.js 的文件路徑賦值給 entryId } const fatherPath = path.dirname(moduleRelativePath) // fatherPath 即獲取 ./src/index.js 的最後一段文件或文件夾的父目錄 => ./src const {sourceCode, dependencies} = this.parse(source, fatherPath) // parse()主要功能 // 1. 對入口文件源碼進行改造 // 2. 返回改造後的源碼 和 依賴列表 // 參數: // 改造前的源碼 // 和父路徑 // 返回值 // 改造後的源碼 // 依賴列表 this.modules[moduleRelativePath] = sourceCode; // this.modules // 模塊的路徑 和 模塊的源碼一一對應 // key => moduleRelativePath // value => sourceCode dependencies.forEach(dep => { // 附模塊的加載 遞歸加載 this.buildModule(path.join(this.root, dep), false) }) // 遞歸依賴數組,將this.modules對象的全部key,vaue收起到一塊兒 } emitFile() { // 發射文件 console.log(111111111) const {path: p, filename} = this.config.output const main = path.join(p, filename) // main 表示打包後的文件的路徑 const templeteSourceStr = this.getSource(path.join(__dirname, 'main.ejs')) // 讀取模塊源文件 main.ejs const code = ejs.render(templeteSourceStr, { entryId: this.entryId, modules: this.modules }) // 渲染模板 // 模板中有兩個參數 entryId 和 modules this.assets = {} this.assets[main] = code; // key:打包後的文件路徑 // value: 打包後的文件源碼 fs.writeFileSync(main, this.assets[main]) // 寫文件按 // fs.writeFileSync(file, data[, options]) } run() { // run方法主要作兩件事情 // 1. 建立模塊的依賴關係 // 2. 發射打包後的文件 this.buildModule(path.resolve(this.root, this.entry), true) // buildModule()的做用:建模塊的依賴關係 // 參數: // 第一個參數:是entry指定路徑的絕對路徑 // 第二個參數:是不是主模塊 console.log(this.modules, this.entryId) // 發射一個文件,打包後的文件 this.emitFile() } } module.exports = Compiler 複製代碼

資料

打包原理: www.jianshu.com/p/89bd63d25…
打包原理2:juejin.im/post/5d81cc…
Webpack Loader:juejin.im/post/5a698a…

相關文章
相關標籤/搜索