webpack
打包后源码解读
main.js
index.js
js
import { str } from './a'
console.log(str)
a.js
js
export const str = 'a.js'
打包如下:
js
/*
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
/******/ ;(() => {
// webpackBootstrap
/******/ 'use strict'
/******/ var __webpack_modules__ = {
/***/ './src/a.js':
/*!******************!*\
!*** ./src/a.js ***!
\******************/
/***/ (
__unused_webpack_module,
__webpack_exports__,
__webpack_require__
) => {
eval(
'__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "str": () => (/* binding */ str)\n/* harmony export */ });\nconst str = \'a.js\'\n\n\n//# sourceURL=webpack://demo/./src/a.js?'
)
/***/
},
/***/ './src/index.js':
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/***/ (
__unused_webpack_module,
__webpack_exports__,
__webpack_require__
) => {
eval(
'__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a */ "./src/a.js");\n\nconsole.log(_a__WEBPACK_IMPORTED_MODULE_0__.str)\n\n\n//# sourceURL=webpack://demo/./src/index.js?'
)
/***/
},
/******/
} // The module cache
/************************************************************************/
/******/ /******/ var __webpack_module_cache__ = {} // The require function
/******/
/******/ /******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId]
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports
/******/
} // Create a new module (and put it into the cache)
/******/ /******/ var module = (__webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {},
/******/
}) // Execute the module function
/******/
/******/ /******/ __webpack_modules__[moduleId](
module,
module.exports,
__webpack_require__
) // Return the exports of the module
/******/
/******/ /******/ return module.exports
/******/
} /* webpack/runtime/define property getters */
/******/
/************************************************************************/
/******/ /******/ ;(() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for (var key in definition) {
/******/ if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
/******/ Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
})
/******/
}
/******/
}
/******/
}
/******/
})() /* webpack/runtime/hasOwnProperty shorthand */
/******/
/******/ /******/ ;(() => {
/******/ __webpack_require__.o = (obj, prop) =>
Object.prototype.hasOwnProperty.call(obj, prop)
/******/
})() /* webpack/runtime/make namespace object */
/******/
/******/ /******/ ;(() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = exports => {
/******/ if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, {
value: 'Module',
})
/******/
}
/******/ Object.defineProperty(exports, '__esModule', { value: true })
/******/
}
/******/
})() // startup // Load entry module and return exports // This entry module can't be inlined because the eval devtool is used.
/******/
/************************************************************************/
/******/
/******/ /******/ /******/ /******/ var __webpack_exports__ = __webpack_require__(
'./src/index.js'
)
/******/
/******/
})()
精简注释后:
js
;(() => {
// webpack 启动
'use strict'
var webpackmodules = {
'./src/a.js': (unusedmodule, exports, require) => {
//为exports打标记,__esModules
require.r(exports)
//将原始js导出的内容映射到exports上
require.d(exports, { str: () => str })
const str = 'a.js'
},
'./src/index.js': (unusedmodule, exports, require) => {
require.r(exports)
var a = require('./src/a.js')
console.log(a.str)
},
}
// 模块缓存
var modulecache = {}
// The require function
function require(moduleId) {
// 尝试取缓存
var cachedModule = modulecache[moduleId]
if (cachedModule !== undefined) {
return cachedModule.exports
}
// 创建一个模块,写入缓存
var module = (modulecache[moduleId] = {
exports: {},
})
// 执行模块
webpackmodules[moduleId](module, module.exports, require)
// 返回模块
return module.exports
}
;(() => {
//definition就是原始模块的具体内容(即a.js export的内容)
//调用require会得到一个module,module中有一个exports
//将definition中的每一个key映射到exports中
require.d = (exports, definition) => {
for (var key in definition) {
if (require.o(definition, key) && !require.o(exports, key)) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
})
}
}
}
})()
// 就是hasOwnProperty方法
;(() => {
require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)
})()
// 为exports定义__esModules属性
;(() => {
require.r = exports => {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
// Symbol.toStringTag:内置 symbol,它通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签,通常只有内置的 Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里
// 即:某个exports调用toString()会返回 "[object Module]"
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
}
Object.defineProperty(exports, '__esModule', { value: true })
}
})()
//入口js
//这里的./src/index.js只是一个key
var exports = require('./src/index.js')
})()
sourcemap
js
{
devtool: 'eval-cheap-module-source-map' //开发
//devtool:'cheap-module-source-map' //生产
}
简易 webpack 实现
入口文件 index.js,依赖 a.js,b.js
index.js
js
import { str as stra } from './a'
import { str as strb } from './b'
console.log('index.js', stra, strb)
a.js
js
export const str = 'a.js'
b.js
javascript
export const str = 'b.js'
执行打包文件,bundle.js
js
const MiniWebpack = require('./lib/miniwebpack')
const config = require('./webpack.config')
new MiniWebpack(config).run()
webpack 配置文件,webpack.config.js
js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: { path: path.resolve(__dirname, './dist'), filename: 'main.js' },
}
miniwebpack 具体实现,miniwebpack.js
js
//babel使用的JavaScript解析器,得到ast
const parser = require('@babel/parser')
//遍历ast,解析出依赖
const traverse = require('@babel/traverse').default
const { transformFromAst } = require('@babel/core')
const path = require('path')
const fs = require('fs')
class MiniWebpack {
constructor(options) {
//入口
this.entry = options.entry
//出口
this.output = options.output
//模块
this.modules = []
}
run() {
//解析
const info = this.parse(this.entry)
this.modules.push(info)
for (let i = 0; i < this.modules.length; i++) {
const { dependencies } = this.modules[i]
if (dependencies) {
//遍历依赖,解析依赖
for (let j in dependencies) {
this.modules.push(this.parse(dependencies[j]))
}
}
}
//modules中就保存了所有的模块,并且知道每个模块依赖那些模块
const obj = {}
this.modules.forEach(item => {
obj[item.entryFile] = { dependencies: item.dependencies, code: item.code }
})
// 生成出口文件
this.file(obj)
}
file(code) {
const filepath = path.join(this.output.path, this.output.filename)
//转为字符串
const newcode = JSON.stringify(code)
const bundle = `;(function(graph){
//自定义require
function require(module){
function otherRequire(relativePath){
return require(graph[module].dependencies[relativePath])
}
var exports = {}
;(function(require,exports,code){
//动态执行代码
eval(code)
})(otherRequire,exports,graph[module].code)
return exports
}
//先获取入口文件
require('${this.entry}')
})(${newcode})`
//写到指定的出口文件
fs.writeFileSync(filepath, bundle)
}
//解析入口文件
parse(entryFile) {
//读取文件
const content = fs.readFileSync(entryFile).toString()
//转化为ast
const ast = parser.parse(content, {
sourceType: 'module',
})
const dependencies = {}
//遍历ast,解析出依赖那些模块
traverse(ast, {
ImportDeclaration: ({ node }) => {
// 将依赖收集到dependencies
dependencies[node.source.value] =
'./' + path.join(path.dirname(entryFile), node.source.value) + '.js'
},
})
// 从ast转换出代码,
// 允许您使用最新的 JavaScript,而无需对目标环境需要哪些语法转换(以及可选的浏览器 polyfill)进行微观管理
const { code } = transformFromAst(ast, null, {
presets: ['@babel/preset-env'],
})
return {
entryFile,
dependencies,
code,
}
}
}
module.exports = MiniWebpack
输出文件 main.js
js
;(function (graph) {
//自定义require
function require(module) {
function otherRequire(relativePath) {
return require(graph[module].dependencies[relativePath])
}
var exports = {}
;(function (require, exports, code) {
//动态执行代码
eval(code)
})(otherRequire, exports, graph[module].code)
return exports
}
//先获取入口文件
require('./src/index.js')
})({
'./src/index.js': {
dependencies: { './a': './src/a.js', './b': './src/b.js' },
code: '"use strict";\n\nvar _a = require("./a");\n\nvar _b = require("./b");\n\nconsole.log(\'index.js\', _a.str, _b.str);',
},
'./src/a.js': {
dependencies: {},
code: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.str = void 0;\nvar str = \'a.js\';\nexports.str = str;',
},
'./src/b.js': {
dependencies: {},
code: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.str = void 0;\nvar str = \'b.js\';\nexports.str = str;',
},
})vite
roadhog react-app-rewired
roadhog > af-webpack react-app-rewired > customize-cra
css-loader
现象:
css
body {
background-image: url('./a.png');
}
报错:Module not found: Error: Can't resolve './a.png' in 'xxx'
实际 enhanced-resolve Resolver.resolve 报错
css
body {
background-image: url('/a.png');
}
正常
老版本(<=3.x)的 css-loader 中,用 loader-utils 包的 isUrlRequest 方法判断 url 是否需要 require loader-utils <= 2.x / 开头不require https://github.com/webpack/loader-utils/blob/6688b5028106f144ee9f543bebc8e6a87b57829f/lib/isUrlRequest.js#L24
资源路径 output.publicPath + output.filname 设置__webpack_public_path__后,相当于替代了output.publicPath
即是动态的 webpack_public_path + output.filname