笔记内容大多出自于拉勾教育大前端高薪训练营的教程,因此也许会和其他文章雷同较多,请担待。
$ npm i webapck webpack-cli --dev
$ md webpack.config.js
// webpack.config.js
const path = require('path')
module.exports = {
mode: 'development', // 打包模式 development / production
entry: './src/index.js', // 打包入口文件
output: {
filename: 'bundle.js', // 输出文件名
path: path.join(__dirname, 'dist'), // 输出路径,因为必须绝对路径,所以使用path来生成绝对路径
publicPath: 'dist/' // 网站的根目录,因为在未打包index.html时已完成打包的资源文件会进入dist,但对于没有打包的index.html来说并不会改变引用路径,所以需要重新指定资源文件的路径,从dist下面寻找
},
module: {
rules: [
{
test: /.css$/,
loader: [
'style-loader', // 将css文件打包成的js文件进行引用
'css-loader' // 将css文件打包成js文件
]
},
/* ---------------- start ---------------- */
// 将小文件交给url-loader去转换为Base64,优化加载速率
// 将大文件交给file-loader去生成新资源并修改路径,因为大文件转换为Base64之后也许会更大
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: 'file-loader' // 该loader可以处理图片、音视频、字体的路径
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader', // 该loader可以处理图片、音视频、字体的本体,将其转换为Base64
options: {
limit: 10 * 1024 // 10kb
}
}
}
/* ---------------- end ---------------- */
]
}
}
// dist/main.js
(function (modules) {
// 当模块已被加载则会放入缓存
var installedModules = {};
// 核心作用是返回模块的exports
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
}
// 将传入的模块缓存到属性 m 上 - modules
__webpack_require__.m = modules
// 将已加载模块缓存到属性 c 上 - cache
__webpack_require__.c = installedModules
// 判断对象身上是否有某属性 - hasOwnProperty
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property)
}
// 为exports定义属性 - defineProperty
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
enumerable: true,
get: getter
})
}
}
// 为exports挂载__esModule: true属性 - redefine
__webpack_require__.r = function (exports) {
// 判断是否是es6,简而言之就是为了判断是否是ES Module
// Object.property.toString.call(exports) -> Module
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: 'Module'
})
}
Object.defineProperty(exports, '__esModule', {
value: true
})
}
__webpack_require__.t = function (value, mode) {
if (mode & 1) value = __webpack_require__(value)
if (mode & 8) return value
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value
var ns = Object.create(null)
__webpack_require__.r(ns)
Object.defineProperty(ns, 'default', {
enumerable: true,
value: value
})
if (mode & 2 && typeof value != 'string') {
for (var key in value) {
__webpack_require__.d(ns, key, function (key) {
return value[key]
}.bind(null, key)
)
}
}
return ns
}
__webpack_require__.n = function (module) {
var getter = module && module.__esModule ?
function getDefault() {
return module['default']
} :
function getModuleExports() {
return module
}
__webpack_require__.d(getter, 'a', getter)
return getter
}
// path
__webpack_require__.p = ""
return __webpack_require__(__webpack_require__.s = "./src/index.js")
})
// index.js
const header = require('./header')
console.log('index 内容 -> next:', header)
module.exports = 'here entry'
// header.js
console.log('header 内容')
module.exports = 'here header'
// dist/main.js
({
"./src/header.js":
(function (module, exports) {
console.log('header 内容')
module.exports = 'here header'
}),
"./src/index.js":
(function (module, exports, __webpack_require__) {
const header = __webpack_require__("./src/header.js")
console.log('index 内容 -> next:', header)
module.exports = 'here entry'
})
})
// index.js
const header = require('./header')
console.log('index 内容 -> next:', header)
module.exports = 'here entry'
// header.js
console.log('header 内容')
export default 'here header'
// dist/main.js
({
"./src/header.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__)
console.log('header 内容')
__webpack_exports__["default"] = ('here header')
}),
"./src/index.js":
(function (module, exports, __webpack_require__) {
const header = __webpack_require__("./src/header.js")
console.log('index 内容 -> next:', header)
module.exports = 'here entry'
})
})
// index.js
import header from './header'
console.log('index 内容 -> next:', header)
export default 'here entry'
// header.js
console.log('header 内容')
module.exports = 'here header'
// dist/main.js
({
"./src/header.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__)
console.log('header 内容')
__webpack_exports__["default"] = ('here header')
}),
"./src/index.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__)
var _header__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/header.js")
console.log('index 内容 -> next:', _header__WEBPACK_IMPORTED_MODULE_0__["default"])
__webpack_exports__["default"] = ('here entry')
})
})
// index.js
import header from './header'
console.log('index 内容 -> next:', header)
export const message = 'here entry'
// header.js
console.log('header 内容')
export default 'here header'
// dist/main.js
({
"./src/header.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__)
console.log('header 内容')
__webpack_exports__["default"] = ('here header')
}),
"./src/index.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__)
__webpack_require__.d(__webpack_exports__, "message", function () {
return message
})
var _header__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/header.js")
console.log('index 内容 -> next:', _header__WEBPACK_IMPORTED_MODULE_0__["default"])
const message = 'here entry'
})
})
(function (modules) {
function webpackJsonpCallback(data) {
var chunkIds = data[0]
var moreModules = data[1]
var moduleId, chunkId, i = 0, resolves = [];
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i]
if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0])
}
installedChunks[chunkId] = 0
}
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId]
}
}
if (parentJsonpFunction) parentJsonpFunction(data)
while (resolves.length) {
resolves.shift()()
}
}
var installedModules = {}
// 0 已加载
// undefine 未加载
// promise 加载中
var installedChunks = {
"main": 0
}
function jsonpScriptSrc(chunkId) {
return __webpack_require__.p + "" + chunkId + ".main.js"
}
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
}
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = []
var installedChunkData = installedChunks[chunkId]
if (installedChunkData !== 0) {
if (installedChunkData) {
promises.push(installedChunkData[2])
} else {
var promise = new Promise(function (resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject]
})
promises.push(installedChunkData[2] = promise)
var script = document.createElement('script')
var onScriptComplete
script.charset = 'utf-8'
script.timeout = 120
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc)
}
script.src = jsonpScriptSrc(chunkId);
var error = new Error()
onScriptComplete = function (event) {
script.onerror = script.onload = null
clearTimeout(timeout);
var chunk = installedChunks[chunkId]
if (chunk !== 0) {
if (chunk) {
var errorType = event && (event.type === 'load' ? 'missing' : event.type)
var realSrc = event && event.target && event.target.src
error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'
error.name = 'ChunkLoadError'
error.type = errorType
error.request = realSrc
chunk[1](error)
}
installedChunks[chunkId] = undefined
}
}
var timeout = setTimeout(function () {
onScriptComplete({
type: 'timeout',
target: script
})
}, 120000)
script.onerror = script.onload = onScriptComplete
document.head.appendChild(script)
}
}
return Promise.all(promises)
};
__webpack_require__.m = modules
__webpack_require__.c = installedModules
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
enumerable: true,
get: getter
})
}
}
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: 'Module'
})
}
Object.defineProperty(exports, '__esModule', {
value: true
})
}
__webpack_require__.t = function (value, mode) {
/*
- 接收2个参数,value一般是moduleId,mode是一个二进制数值
- 首先使用__webpack_require__加载模块覆盖value的值
- 当(mode & 8)成立时直接返回value(CommonJS)
- 当(mode & 4)成立时直接返回value(ES Module)
- 当上述都不成立,则需要定义一个命名空间 ns {}
+ 如果value是一个可以直接使用的值,比如字符串/数值,则将它挂载到 ns的 default 属性上
+ 否则遍历value,将value的每个属性都挂载到 ns 上,然后返回 ns
*/
if (mode & 1) value = __webpack_require__(value)
if (mode & 8) return value
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value
var ns = Object.create(null)
__webpack_require__.r(ns)
Object.defineProperty(ns, 'default', {
enumerable: true,
value: value
})
if (mode & 2 && typeof value != 'string') {
for (var key in value) {
__webpack_require__.d(ns, key, function (key) {
return value[key]
}.bind(null, key)
)
}
}
return ns
}
__webpack_require__.n = function (module) {
var getter = module && module.__esModule ?
function getDefault() {
return module['default']
} :
function getModuleExports() {
return module
}
__webpack_require__.d(getter, 'a', getter)
return getter
}
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property)
}
__webpack_require__.p = "";
__webpack_require__.oe = function (err) {
console.error(err)
throw err
};
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray)
jsonpArray.push = webpackJsonpCallback
jsonpArray = jsonpArray.slice()
for (var i = 0; i < jsonpArray.length; i++) {
webpackJsonpCallback(jsonpArray[i])
}
var parentJsonpFunction = oldJsonpFunction
return __webpack_require__(__webpack_require__.s = "./src/index.js")
})
<body>
<div id="btn">buttondiv>
body>
// index.js
console.log('index 内容')
document.getElementById('btn').addEventListener('click', () => {
import(/* webpackChunkName: "button" */'./header').then(data => {
console.log(data)
})
})
module.exports = 'here entry'
// header.js
console.log('header 内容')
module.exports = 'here header'
// dist/button.main.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["button"], {
"./src/header.js":
(function (module, exports) {
console.log('header 内容')
module.exports = 'here header'
})
}])
// dist/main.js
({
"./src/index.js":
(function (module, exports, __webpack_require__) {
console.log('index 内容')
document.getElementById('btn').addEventListener('click', () => {
__webpack_require__.e("button").then(__webpack_require__.t.bind(null, "./src/header.js", 7)).then(data => {
console.log(data)
})
})
module.exports = 'here entry'
})
})
// index.js
console.log('index 内容')
document.getElementById('btn').addEventListener('click', () => {
import(/* webpackChunkName: "button" */'./header').then(data => {
console.log(data)
})
})
module.exports = 'here entry'
// header.js
console.log('header 内容')
export default 'here header'
// dist/button.main.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["button"], {
"./src/header.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__);
console.log('header 内容')
__webpack_exports__["default"] = ('here header')
})
}])
// dist/main.js
({
"./src/index.js":
(function (module, exports, __webpack_require__) {
console.log('index 内容')
document.getElementById('btn').addEventListener('click', () => {
__webpack_require__.e("button").then(__webpack_require__.bind(null, "./src/header.js")).then(data => {
console.log(data)
})
})
module.exports = 'here entry'
})
})
// index.js
console.log('index 内容')
document.getElementById('btn').addEventListener('click', () => {
import(/* webpackChunkName: "button" */'./header').then(data => {
console.log(data)
})
})
export default 'here entry'
// header.js
console.log('header 内容')
module.exports = 'here header'
// dist/button.main.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["button"], {
"./src/header.js":
(function (module, exports) {
console.log('header 内容')
module.exports = 'here header'
})
}])
// dist/main.js
({
"./src/index.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
console.log('index 内容')
document.getElementById('btn').addEventListener('click', () => {
__webpack_require__.e("button").then(__webpack_require__.t.bind(null, "./src/header.js", 7)).then(data => {
console.log(data)
})
})
__webpack_exports__["default"] = ('here entry')
})
})
// index.js
console.log('index 内容')
document.getElementById('btn').addEventListener('click', () => {
import(/* webpackChunkName: "button" */'./header').then(data => {
console.log(data)
})
})
export default 'here entry'
// header.js
console.log('header 内容')
export default 'here header'
// dist/button.main.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["button"], {
"./src/header.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__)
console.log('header 内容')
__webpack_exports__["default"] = ('here header')
})
}])
// dist/main.js
({
"./src/index.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__)
__webpack_require__.d(__webpack_exports__, "message", function () {
return message
})
console.log('index 内容')
document.getElementById('btn').addEventListener('click', () => {
__webpack_require__.e("button").then(__webpack_require__.bind(null, "./src/header.js")).then(data => {
console.log(data)
})
})
const message = 'here entry'
})
})
原理
$ node run
// root/run.js
const webpack = require('./pack/lib/webpack')
const options = require('./webpack.config.js')
const compiler = webpack(options)
compiler.run((err, stats) => {
console.log(err)
console.log(stats)
})
// lib/temp/temp.ejs
(function (modules) {
let installedModules = {};
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
let module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
__webpack_require__.o = function (obj, name) {
return Object.prototype.hasOwnProperty(obj, name);
}
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
enumerable: true,
get: getter
});
}
}
__webpack_require__.r = function (exports) {
if (typeof Symbol !== undefined && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: 'Module'
});
}
Object.defineProperty(exports, '__esModule', {
value: true
});
}
let installedChunks = {
"main": 0
}
function webpackJsonpCallback(data) {
const chunkIds = data[0];
const moreModules = data[1];
let resolves = [];
for (let i = 0; i < chunkIds.length; i++) {
const chunkId = chunkIds[i]; if (installedChunks[chunkId] &&
Object.prototype.hasOwnProperty.call(installedChunks, chunkId)) { resolves.push(installedChunks[chunkId][0]); }
installedChunks[chunkId] = 0;
} for (let moduleId in moreModules) { modules[moduleId] = moreModules[moduleId]; } while
(resolves.length) { resolves.shift()(); }
} __webpack_require__.e = function (chunkId) {
let promises = []; let
installedChunkData = installedChunks[chunkId]; if (installedChunkData !== 0) {
if (installedChunkData) {
promises.push(installedChunkData[2]);
} else {
let promise = new Promise((resolve, reject) => {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
})
promises.push(installedChunkData[2] = promise);
let script = document.createElement('script');
script.src = jsonpScriptSrc(chunkId);
document.head.appendChild(script);
}
}
return Promise.all(promises);
}
function jsonpScriptSrc(chunkId) {
return __webpack_require__.p + '' + chunkId + '.main.js';
}
let jsonpArray = window['webpackJsonp'] = window['webpackJsonp'] = [];
let originJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
let parentJsonpFunction = originJsonpFunction;
jsonpArray.forEach(jsonp => {
webpackJsonpCallback(jsonp);
})
__webpack_require__.t = function (value, mode) {
if (mode & 1) value = __webpack_require__(value);
if (mode & 8) return value;
if (mode & 4 && typeof value === 'object' && value && value.__esModule) return value;
let namespace = Object.create(null);
Object.defineProperty(namespace, 'default', {
enumerable: true,
value
});
if (mode & 2 && typeof value !== 'string') {
for (let key in value) {
__webpack_require__.d(namespace, key, (key => value[key]).bind(null, key));
}
}
return namespace;
}
__webpack_require__.p = '';
return __webpack_require__(__webpack_require__.s = '<%-entryModuleId%>');
})
({
<%for (let module of modules) {%>
"<%-module.moduleId%>":
(function (module, exports, __webpack_require__) {
<%-module._source %>
}),
<%}%>
})
// lib/webpack.js
const Compiler = require("./Compiler")
const WebpackOptionsApply = require("./WebpackOptionsApply")
const NodeEnviromentPlugin = require("./node/NodeEnviromentPlugin")
const webpack = options => {
// 实例化 Compiler 对象
let compiler = new Compiler(options.context)
compiler.options = options
// 初始化 NodeEnviromentPlugin 让compiler对象具有可读写能力
new NodeEnviromentPlugin().apply(compiler)
// 挂载所有plugins至compiler对象身上
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
plugin.apply(compiler)
}
}
// 挂载所有 webpack 内置插件
// compiler.options = new WebpackOptionsApply().process(options, compiler)
new WebpackOptionsApply().process(options, compiler)
return compiler
}
module.exports = webpack
// lib/node/NodeEnviromentPlugin.js
const fs = require('fs')
module.exports = class {
constructor(options) {
this.options = options || {}
}
apply(compiler) {
compiler.inputFileSystem = fs
compiler.outputFileSystem = fs
return compiler
}
}
// lib/WebpackOptionsApply.js
const EntryOptionPlugin = require("./EntryOptionPlugin")
module.exports = class {
process(options, compiler) {
new EntryOptionPlugin().apply(compiler)
compiler.hooks.entryOption.call(options.context, options.entry)
}
}
// lib/Compiler.js
const {
Tapable,
SyncHook,
SyncBailHook,
AsyncSeriesHook,
AsyncParallelHook
} = require('tapable')
const path = require('path')
const mkdirp = require('mkdirp')
const Stats = require('./Stats')
const NormalModuleFactory = require('./NormalModuleFactory')
const Compilation = require('./Compilation')
module.exports = class extends Tapable {
constructor(context) {
super()
this.context = context
this.hooks = {
done: new AsyncSeriesHook(['stats']),
entryOption: new SyncBailHook(['context', 'entry']),
beforeCompile: new AsyncSeriesHook(['params']),
compile: new SyncHook(['params']),
make: new AsyncParallelHook(['compilation']),
afterCompile: new AsyncSeriesHook(['compilation']),
beforeRun: new AsyncSeriesHook(["compiler"]),
run: new AsyncSeriesHook(["compiler"]),
thisCompilation: new SyncHook(["compilation", "params"]),
compilation: new SyncHook(["compilation", "params"]),
emit: new AsyncParallelHook(['compilation'])
}
}
run(callback) {
console.log('run 执行')
const finalCallback = (err, stats) => {
callback(err, stats)
}
const onCompiled = (err, compilation) => {
console.log('onCompiled')
finalCallback(err, new Stats(compilation))
// 将处理好的 chunk 写入到指定文件
this.emitAssets(compilation, err => {
finalCallback(err, new Stats(compilation))
})
}
this.hooks.beforeRun.callAsync(this, err => {
this.hooks.run.callAsync(this, err => {
this.compile(onCompiled)
})
})
}
emitAssets(compilation, callback) {
// 创建 dist 目录
// 定义一个工具方法用于执行文件的生成
const emitFiles = err => {
const assets = compilation.assets
const outputPath = this.options.output.path
for (let file in assets) {
let source = assets[file]
let targetPath = path.posix.join(outputPath, file)
this.outputFileSystem.writeFileSync(targetPath, source, 'utf8')
}
callback(err)
}
// 创建目录之后启动文件写入
this.hooks.emit.callAsync(compilation, err => {
console.log(this.options);
mkdirp.sync(this.options.output.path)
emitFiles(err)
})
}
newCompilationParams() {
const params = {
normalModuleFactory: new NormalModuleFactory()
}
return params
}
newCompilation(params) {
const compilation = this.createCompilation(params)
this.hooks.thisCompilation.call(compilation, params)
this.hooks.compilation.call(compilation, params)
return compilation
}
createCompilation() {
return new Compilation(this)
}
compile(callback) {
const params = this.newCompilationParams()
this.hooks.beforeCompile.callAsync(params, err => {
this.hooks.compile.call(params)
const compilation = this.newCompilation(params)
this.hooks.make.callAsync(compilation, err => {
console.log('make 钩子触发')
compilation.seal(err => {
this.hooks.afterCompile.callAsync(compilation, err => {
callback(err, compilation)
})
})
})
})
}
}
// lib/Compilation.js
const path = require('path')
const ejs = require('ejs')
const async = require('neo-async')
const NormalModuleFactory = require('./NormalModuleFactory')
const Parser = require('./Parser')
const Chunk = require('./Chunk')
const { SyncHook, Tapable } = require('tapable')
const parser = new Parser()
module.exports = class extends Tapable {
constructor(compiler) {
super()
this.compiler = compiler
this.context = compiler.context
this.options = compiler.options
// 让 compilation 具备文件读写能力
this.inputFileSystem = compiler.inputFileSystem
this.outputFileSystem = compiler.outputFileSystem
this._entries = []
this.modules = []
this.chunks = []
this.assets = []
this.files = []
this.hooks = {
successedModule: new SyncHook(['module']),
seal: new SyncHook(),
beforeChunks: new SyncHook(),
afterChunks: new SyncHook(),
}
}
addEntry(context, entry, name, callback) {
this._addModuleChain(context, entry, name, (err, module) => {
callback(err, module)
})
}
_addModuleChain(context, entry, name, callback) {
this.createModule({
parser,
name,
context,
rawRequest: entry,
resource: path.posix.join(context, entry),
moduleId: './' + path.posix.relative(context, entry),
}, entryModule => {
this._entries.push(entryModule)
}, callback)
}
createModule(data, doAddEntry, callback) {
let module = new NormalModuleFactory().create(data)
const afterBuild = (err, module) => {
// 在 afterBuild 当中需要判断当前次 module 加载完成之后是否需要处理依赖加载
if (module.dependencies.length > 0) {
// 当前逻辑表示 module 有需要加载的模块
this.processDependencies(module, err => {
callback(err, module)
})
} else {
callback(err, module)
}
}
this.buildModule(module, afterBuild)
// 完成 build 之后将 module 保存
doAddEntry && doAddEntry(module)
this.modules.push(module)
}
buildModule(module, callback) {
module.build(this, err => {
// 当前 module 完成编译
this.hooks.successedModule.call(module)
callback(err, module)
})
}
processDependencies(module, callback) {
// 实现一个依赖模块的递归加载
// 由于不知道有几个依赖模块,所以使用 neo-async 实现,当模块都加载完毕再调用 callback
let dependencies = module.dependencies
async.forEach(dependencies, (dependency, done) => {
this.createModule({
parser,
name: dependency.name,
context: dependency.context,
rawRequest: dependency.rawRequest,
moduleId: dependency.moduleId,
resource: dependency.resource
}, null, done)
}, callback)
}
seal(callback) {
this.hooks.seal.call()
this.hooks.beforeChunks.call()
// 所有的入口模块都被存放在了 _entries 中
for (const entryModule of this._entries) {
// 创建模块加载已有模块的内容,同时记录模块信息
const chunk = new Chunk(entryModule)
this.chunks.push(chunk)
chunk.modules = this.modules.filter(module => module.name === chunk.name)
}
// chunk 流程梳理之后就进入到 chunk 代码处理环节(模板文件 + 模块源代码 -> chunk.js)
this.hooks.afterChunks.call(this.chunks)
// 生成代码内容
this.createChunkAssets()
callback()
}
createChunkAssets() {
for (let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i]
const fileName = chunk.name + '.js'
chunk.files.push(fileName)
// 生成具体chunk内容
// 获取模板文件路径
let tempPath = path.posix.join(__dirname, 'temp/main.ejs')
// 获取模板源码
let tempCode = this.inputFileSystem.readFileSync(tempPath, 'utf8')
// 获取渲染函数
let tempRender = ejs.compile(tempCode)
// 按ejs的语法渲染
let source = tempRender({
entryModuleId: chunk.entryModule.moduleId,
modules: chunk.modules
})
// 输出文件
this.emitAssets(fileName, source)
}
}
emitAssets(fileName, source) {
this.assets[fileName] = source
this.files.push(fileName)
}
}
// lib/EntryOptionPlugin.js
const SingleEntryPlugin = require("./SingleEntryPlugin")
const itemToPlugin = (context, entry, name) => {
return new SingleEntryPlugin(context, entry, name)
}
module.exports = class {
apply(compiler) {
compiler.hooks.entryOption.tap('EntryOptionPlugin', (context, entry) => {
itemToPlugin(context, entry, 'main').apply(compiler)
})
}
}
// lib/SingleEntryPlugin.js
module.exports = class {
constructor(context, entry, name) {
this.context = context
this.entry = entry
this.name = name
}
apply(compiler) {
compiler.hooks.make.tapAsync('SingleEntryPlugin', (compilation, callback) => {
const {
context,
entry,
name
} = this
console.log('make 钩子监听')
compilation.addEntry(context, entry, name, callback)
})
}
}
// lib/NormalModuleFactory.js
const NormalModule = require('./NormalModule')
module.exports = class {
create(data) {
return new NormalModule(data)
}
}
// lib/Normal.js
const path = require('path')
const types = require('@babel/types')
const generator = require('@babel/generator').default
const traverse = require('@babel/traverse').default
module.exports = class {
constructor(data) {
this.name = data.name
this.context = data.context
this.rawRequest = data.rawRequest
this.resource = data.resource
this.moduleId = data.moduleId
this.parser = data.parser
this._source // 存放某个模块源代码
this._ast // 存放某个模块语法树
this.dependencies = [] // 定义一个空数组用于保存被依赖加载的模块信息
}
build(compilation, callback) {
// 从文件中读取到将来需要被加载的 module 内容
// 如果当前文件不是js,则需要使用 loader 处理,并返回js
// 上述操作完成后就可以将js代码转为ast语法树
// 当前js模块内部可能又引用了其他模块,所以需要递归
this.doBuild(compilation, err => {
this._ast = this.parser.parse(this._source)
traverse(this._ast, {
CallExpression: (nodePath) => {
let node = nodePath.node
if (node.callee.name === 'require') {
// 获取原始请求路径
let modulePath = node.arguments[0].value // './header'
// 取出当前被加载的模块名称
let moduleName = modulePath.split(path.posix.sep).pop()
let extName = moduleName.indexOf('.') === -1 ? '.js' : ''
moduleName += extName // header.js
let depResource = path.posix.join(path.posix.dirname(this.resource), moduleName)
// 定于当前模块的id
let depModuleId = './' + path.posix.relative(this.context, depResource) // ./src/header.js
// 记录当前被依赖模块的信息,方便后面递归加载
this.dependencies.push({
name: this.name,
context: this.context,
rawRequest: moduleName,
moduleId: depModuleId,
resource: depResource
})
// 替换内容
node.callee.name = '__webpack_require__'
node.arguments = [types.stringLiteral(depModuleId)]
}
}
})
let { code } = generator(this._ast)
this._source = code
callback(err)
})
}
doBuild(compilation, callback) {
this.getSource(compilation, (err, source) => {
this._source = source
callback(err)
})
}
getSource(compilation, callback) {
compilation.inputFileSystem.readFile(this.resource, 'utf8', callback)
}
}
// lib/Parser.js
const { Tapable } = require('tapable')
const babylon = require('babylon')
module.exports = class extends Tapable {
parse(source) {
return babylon.parse(source, {
sourceType: 'module',
plugins: ['dynamicImport']
})
}
}
// lib/Chunk.js
module.exports = class {
constructor(entryModule) {
this.entryModule = entryModule
this.name = entryModule.name
this.files = [] // 存放文件名
this.modules = [] // 记录 chunk 包含的 module
}
}
// lib/Stats
module.exports = class {
constructor(compilation) {
this.entries = compilation._entries
this.modules = compilation.modules
this.chunks = compilation.chunks
}
toJson() {
return this
}
}