[深刻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
bin/wpack.js
bin: { wpack: '路徑'}
npm link wpack
npx wpack
源碼:
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"
}
複製代碼
enter(path)進入
和 exit(path)退出
等鉤子babelTypes.stringLiteral(modulePath)
const options = loaderUtils.getOptions(this)vue
( loader ) 是一個 ( 函數 ),函數的第一個參數表示 ( 該loader匹配的文件的 源代碼 )node
loader 不能寫成 ( 箭頭函數 ),由於須要經過this獲取更多的apireact
loader-utilswebpack
npm install loader-utils -D
const options = loaderUtils.getOptions(this)
this.callbackgit
this.asyncgithub
編寫好的loader,如何在webpack.config.js中引入?web
module.exports = {
...
module: {
rules: [{
test: /\.js$/,
use: [{
loader: path.resolve(__dirname, 'loaders/replace-loader'), // 須要用到path模塊
options: {
name: 'aaaaa'
}
}]
}]
}
}
複製代碼
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實例
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)
}
複製代碼
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/']
}
}
複製代碼
(function(modules){
var initialMoudles = {}
function __webpack_require__(moduleId)
return __webpack_require__('./src/index.js')
})()
自執行後,至關於調用 __webpack_require__('./src/index.js'),而且 initialMoudles 成爲閉包變量,常駐內存
複製代碼
{
"./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()源碼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方法,
複製代碼
fs.readFileSync(modulePath, { encoding: 'utf8' })
讀取傳入的模塊路徑對應的源碼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收起到一塊兒
}
複製代碼
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
複製代碼
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(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
}
複製代碼
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} } 複製代碼
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]%>`)
}),
<%}%>
});
複製代碼
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…