当前位置:网站首页>Esbuild Bundler HMR
Esbuild Bundler HMR
2022-07-29 02:25:00 【Ink fragrance^_^】
Esbuild although bundler Very fast , But it does not provide HMR The ability of , In the development process, only live-reload The plan , Once there is a code change , Pages need to be full reload , This greatly reduces the development experience . Add for this HMR Function is very important .
Through investigation and research , There are two kinds of HMR programme , Namely Webpack/ Parcel As a representative of the Bundler HMR and Vite As a representative of the Bundlerless HMR. After consideration , We decided to achieve Bundler HMR, Encountered some problems in the implementation process , Made some notes , I hope you know something .

Front end self study
Every morning , Enjoy a good front-end article .
137 Original content
official account
ModuleLoader Module loader
Esbuild It has Scope hosting The function of , This is the optimization that production mode often turns on , It will improve the execution speed of the code , But this blurs the boundary of the module , It is impossible to distinguish which module the code comes from , For modules HMR It's impossible to talk about , To do this, you need to disable Scope hosting function . because Esbuild Switch not provided , We can only abandon it Bundler result , On their own Bundler.
suffer Webpack inspire , We convert the code in the module into Common JS, Again wrapper To our own Moduler loader Runtime , The circular dependency needs to be exported in advance module.exports You need to pay attention to .
Convert to Common JS Now it's using Esbuild Self contained transform, But we need to pay attention to several issues .
Esbuild dynamic import follow browser target Cannot convert directly require, At present, it is through regular replacement hack.
Esbuild The code transferred out contains some runtime code , Not very clean .
Macros in code (process.env.NODE_ENV etc. ) Attention should be paid to replacement .
For example, the conversion result of the following module code :
// a.ts
import { value } from 'b'
// transformed to
moduleLoader.registerLoader('a'/* /path/to/a */, (require, module, exports) => {
const { value } = require('b');
});
Cjs Dynamic export module features .
export function name(a) {
return a + 1
}
const a = name(2)
export default a
After the conversion of the above modules, the results are as follows :
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var entry_exports = {};
// Note that there
__export(entry_exports, {
default: () => entry_default,
name: () => name
});
module.exports = __toCommonJS(entry_exports);
function name(a2) {
return a2 + 1;
}
var a = name(2);
var entry_default = a;
Pay attention to two parts :
The first 7 Line of code can see ,
ESMturnCJSThe module will be added__esModuleMark .The first 10 You can see in line of code ,CJS The export of is computed Of , module.exports Assignment needs to be preserved computed export .
ModuleLoader The implementation of should be compatible with this behavior , The pseudocode is as follows :
class Module {
_exports = {}
get exports() {
return this._exports
}
set exports(value) {
if(typeof value === 'object' && value) {
if (value.__esModule) {
this._exports.__esModule = true;
}
for (const key in value) {
Object.defineProperty(this._exports, key, {
get: () => value[key],
enumerable: true,
});
}
}
}
}
because Scope Hosting Disabled , stay bundler The import and export of modules cannot be checked during , You can only get the code error during operation ,Webpack There is also this problem .
Module Resolver
Although the module has been converted , But I can't recognize alias,node_modules Equal module .
As the following example , node modular b Can't be executed , Because it was registered /path/to/b
// a.ts
import { value } from 'b'
in addition , because HMR API Accepting sub module updates also requires identifying modules .
module.hot.accpet('b', () => {})
There are two solutions :
Module URL Rewrite
Webpack/Vite And so on , Rewrite the module import path .
Registration map
because Module Rerewrite Need to be right import Modules need to be analyzed , There will be some overhead and workload , For this purpose, use the registration mapping table , Mapping at runtime . as follows :
moduleLoader.registerLoader('a'/* /path/to/a */, (require, module, exports) => {
const { value } = require('b');
expect(value).equal(1);
});
moduleLoader.registerResolver('a'/* /path/to/a */, {
'b': '/path/to/b'
});
HMR
When a module changes , You can update the corresponding module without refreshing the page .
First of all, let's have a look at HMR API Examples of use :
// bar.js
import foo from './foo.js'
foo()
if (module.hot) {
module.hot.accept('./foo.js' ,(newFoo) => {
newFoo.foo()
})
}
In the example above ,bar.js yes ./foo.js Of HMR Boundary , That is, the module that accepts the update . If ./foo.js An update has occurred , Just re execute ./foo.js And execute line 7 callback You can complete the update .
The specific implementation is as follows :
Build module dependency diagram .
stay ModuleLoader In the process , The dependencies between modules are recorded while executing modules .

img
If the module contains module.hot.accept Of HMR API Call marks the module as boundary.

img
When the module changes , The minimum... Associated with this module will be regenerated HMR Bundle, And pass it through websocket The message informs the browser that this module has changed , The browser side looks for boundaries, And start to re execute the module update and the corresponding calllback.

img
Be careful HMR API It is divided into Accept updates from sub modules and Accept self updating , Looking for HMR Boundray The process needs to be distinguished .
at present , Only in ModulerLoader It supports accpet dispose API.
Bundle
Because there is no sequence after module conversion , We can merge the code directly , But this will lack sourcemap.
So , Two schemes have been tried :
Magic-string Bundle + remapping
The pseudocode is as follows :
import MagicString from 'magic-string';
import remapping from '@ampproject/remapping';
const module1 = new MagicString('code1')
const module1Map = {}
const module2 = new MagicString('code2')
const module2Map = {}
function bundle() {
const bundle = new MagicString.Bundle();
bundle.addSource({
filename: 'module1.js',
content: module1
});
bundle.addSource({
filename: 'module2.js',
content: module2
});
const map = bundle.generateMap({
file: 'bundle.js',
includeContent: true,
hires: true
});
remapping(map, (file) => {
if(file === 'module1.js') return module1Map
if(file === 'module2.js') return module2Map
return null
})
return {
code: bundle.toString(),
map:
}
}
After implementation, it is found that there are significant performance bottlenecks in the secondary construction ,remapping No, cache .
Webpack-source
The pseudocode is as follows :
import { ConcatSource, CachedSource, SourceMapSource } from 'webpack-sources';
const module1Map = {}
const module1 = new CachedSource(new SourceMapSource('code1'), 'module1.js', module1Map)
const module2 = new CachedSource(new SourceMapSource('code2'), 'module2.js', module1Map)
function bundle(){
const concatSource = new ConcatSource();
concatSource.add(module1)
concatSource.add(module2)
const { source, map } = concatSource.sourceAndMap();
return {
code: source,
map,
};
}
Its CacheModule There are for each module sourcemap cache, Inside remapping It's a small expense , The secondary construction is dozens of times the performance improvement of scheme 1 .
in addition , because esbuild Because it starts the optimization of production mode ,metafile.inputs Not all modules in , Modules without executable code are missing , So when merging code, you need to find all the modules in the module diagram .
Lazy Compiler( Unrealized )
Pages often contain dynamic import Module , These modules are not necessarily used by the first screen of the page , But has also been Bundler, therefore Webpack Put forward Lazy Compiler .Vite utilize ESM Loader Of unbundler This problem is naturally avoided .
React Refresh
What is React Refresh and how to integrate it .
As introduced , There are two processes .
Pass the source code through react-refresh/babel Plug in for conversion , as follows :
function FunctionDefault() {
return <h1>Default Export Function</h1>;
}
export default FunctionDefault;
The conversion result is as follows :
var _jsxDevRuntime = require("node_modules/react/jsx-dev-runtime.js");
function FunctionDefault() {
return (0, _jsxDevRuntime).jsxDEV("h1", {
children: "Default Export Function"
}, void 0, false, {
fileName: "</Users/bytedance/bytedance/pack/examples/react-refresh/src/FunctionDefault.tsx>",
lineNumber: 2,
columnNumber: 10
}, this);
}
_c = FunctionDefault;
var _default = FunctionDefault;
exports.default = _default;
var _c;
$RefreshReg$(_c, "FunctionDefault");
basis bundler hmr Add some runtime.
var prevRefreshReg = window.$RefreshReg$;
var prevRefreshSig = window.$RefreshSig$;
var RefreshRuntime = require('react-refresh/runtime');
window.$RefreshReg$ = (type, id) => {
RefreshRuntime.register(type, fullId);
}
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
// source code
window.$RefreshReg$ = prevRefreshReg;
window.$RefreshSig$ = prevRefreshSig;
// accept self update
module.hot.accept();
const runtime = require('react-refresh/runtime');
let enqueueUpdate = debounce(runtime.performReactRefresh, 30);
enqueueUpdate();
Entry Add the following code .
const runtime = require('react-refresh/runtime');
runtime.injectIntoGlobalHook(window);
window.$RefreshReg$ = () => {};
window.$RefreshSig$ = () => type => type;
Note that this code needs to run in
react-domBefore .
边栏推荐
- 响应式织梦模板家装装饰类网站
- 发布融资需求1.29亿元,大科城项目路演持续浇灌科创“好苗子”
- Day 14: continued day 13 label related knowledge
- 响应式织梦模板化妆美妆类网站
- 字符流综合练习解题过程
- autoware中ndtmatching功能加载点云图坐标系修正的问题
- RGBD点云降采样
- "Wei Lai Cup" 2022 Niuke summer multi school training camp 2, sign in question GJK
- ResNet50+k折交叉验证+数据增强+画图(准确率、召回率、F值)
- Awvs cannot start problem
猜你喜欢
![[mqtt from introduction to improvement series | 09] Wireshark packet capturing and analyzing mqtt messages](/img/dd/c7ad9addb0d15611d996db87bf469f.png)
[mqtt from introduction to improvement series | 09] Wireshark packet capturing and analyzing mqtt messages

第十五天(VLAN相关知识)

Custom MVC principle and framework implementation

STM32 DMA receives serial port data

MySQL stores JSON format data

我被这个浏览了 746000 次的问题惊住了

Excel 打开包含汉字的 csv 文件出现乱码该怎么办?

基于C51实现数码管的显示

awvs无法启动问题

年中总结 | 与自己对话,活在当下,每走一步都算数
随机推荐
字符流综合练习解题过程
Excel 打开包含汉字的 csv 文件出现乱码该怎么办?
如何利用 RPA 实现自动化获客?
[upload picture 2-cropable]
Awvs cannot start problem
ES6 语法扩展
“两个披萨”团队的分支管理实践
【ONE·Data || 数组堆简单实现及其延伸】
“蔚来杯“2022牛客暑期多校训练营2,签到题GJK
TI C6000 TMS320C6678 DSP+ Zynq-7045的PS + PL异构多核案例开发手册(2)
Idea connection database
Practice and experience of security compliance in instant messaging scenarios
【上传图片可剪裁-1】
开启TLS加密的Proftpd安全FTP服务器安装指南
当Synchronized遇到这玩意儿,有个大坑,要注意
【上传图片2-可裁剪】
千万不要把Request传递到异步线程里面,有坑
Hexadecimal to string
Derivation of Euler angle differential equation
基于C51实现数码管的显示