当前位置:网站首页>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 ,
ESM
turnCJS
The module will be added__esModule
Mark .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-dom
Before .
边栏推荐
- 【上传图片可剪裁-1】
- MySQL驱动中关于时间的坑
- npm install 报错 Error: EPERM: operation not permitted, rename
- "Activity recommendation" rush rush! 2022 international open source Festival has new content
- RGBD点云降采样
- The problem of modifying the coordinate system of point cloud image loaded by ndtmatching function in autoware
- Jetpack -- understand the use of ViewModel and livedata
- Jetpack -- navigation realizes page Jump
- PS + PL heterogeneous multicore case development manual for Ti C6000 tms320c6678 DSP + zynq-7045 (2)
- QT qstackedwidget multi interface switching
猜你喜欢
npm install 报错 Error: EPERM: operation not permitted, rename
【MQTT从入门到提高系列 | 09】WireShark抓包分析MQTT报文
QT qstackedwidget multi interface switching
响应式织梦模板家装装饰类网站
4年测试经验,好不容易进了阿里,两个月后我选择了裸辞...
[cloud native] what is the microservice architecture
指针——黄金阶段
Idea connection database
webview攻击
How much is the report development cost in the application system?
随机推荐
Keil5 open the engineering prompt not found device solution
如果时间不够,无法进行充分的测试怎么办?
[one · data | chained binary tree]
进程间通信---对管道的详细讲解(图文案例讲解)
2022.7.27-----leetcode.592
How to write, load and unload plug-ins in QT
详解JS的四种异步解决方案:回调函数、Promise、Generator、async/await
结合Retrofit 改造OKHttp 缓存
如果非要在多线程中使用 ArrayList 会发生什么?
Prometheus + alertmanager message alert
2022.7.27-----leetcode.592
Derivation of Euler angle differential equation
How to guarantee password security? How does the secure browser manage passwords?
NPM install reports an error: eperm: operation not permitted, rename
多边形点测试
Character flow comprehensive exercise problem solving process
3D模型格式全解|含RVT、3DS、DWG、FBX、IFC、OSGB、OBJ等70余种
Remember error scheduler once Asynceventqueue: dropping event from queue shared causes OOM
Kubesphere-多节点安装
如何利用 RPA 实现自动化获客?