Skip to content

Commit

Permalink
Embed React Fast Refresh in Bun
Browse files Browse the repository at this point in the history
Fixes https://github.com/Jarred-Sumner/bun/issues/62

If the project has it's own copy of react fast refresh and is bundling, it will use that instead.
  • Loading branch information
Jarred-Sumner committed Jan 30, 2022
1 parent 711e0ce commit 857e9be
Show file tree
Hide file tree
Showing 14 changed files with 327 additions and 80 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ yarn.lock
dist
*.log
*.out.js
*.out.refresh.js
/package-lock.json
build
*.wat
Expand Down
9 changes: 9 additions & 0 deletions src/import_record.zig
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@ pub const ImportRecord = struct {

kind: ImportKind,

tag: Tag = Tag.none,

pub const Tag = enum {
none,
react_refresh,
jsx_import,
jsx_classic,
};

pub const PrintMode = enum {
normal,
import_path,
Expand Down
135 changes: 75 additions & 60 deletions src/js_parser/js_parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,7 @@ const StaticSymbolName = struct {
pub const __HMRModule = NewStaticSymbol("HMR");
pub const __HMRClient = NewStaticSymbol("Bun");
pub const __FastRefreshModule = NewStaticSymbol("FastHMR");
pub const __FastRefreshRuntime = NewStaticSymbol("FastRefresh");

pub const @"$$m" = NewStaticSymbol("$$m");

Expand Down Expand Up @@ -2468,33 +2469,35 @@ pub const Parser = struct {
if (p.options.features.react_fast_refresh) {
defer did_import_fast_refresh = true;
p.resolveGeneratedSymbol(&p.jsx_refresh_runtime);
const refresh_runtime_symbol: *const Symbol = &p.symbols.items[p.jsx_refresh_runtime.ref.inner_index];
if (!p.options.jsx.use_embedded_refresh_runtime) {
const refresh_runtime_symbol: *const Symbol = &p.symbols.items[p.jsx_refresh_runtime.ref.inner_index];

declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_refresh_runtime.ref, .is_top_level = true };
declared_symbols_i += 1;

const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime);
p.import_records.items[import_record_id].tag = .react_refresh;
jsx_part_stmts[stmt_i] = p.s(S.Import{
.namespace_ref = p.jsx_refresh_runtime.ref,
.star_name_loc = loc,
.is_single_line = true,
.import_record_index = import_record_id,
}, loc);
declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_refresh_runtime.ref, .is_top_level = true };
declared_symbols_i += 1;

stmt_i += 1;
p.named_imports.put(
p.jsx_refresh_runtime.ref,
js_ast.NamedImport{
.alias = refresh_runtime_symbol.original_name,
.alias_is_star = true,
.alias_loc = loc,
const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime);
p.import_records.items[import_record_id].tag = .react_refresh;
jsx_part_stmts[stmt_i] = p.s(S.Import{
.namespace_ref = p.jsx_refresh_runtime.ref,
.star_name_loc = loc,
.is_single_line = true,
.import_record_index = import_record_id,
},
) catch unreachable;
p.is_import_item.put(p.allocator, p.jsx_refresh_runtime.ref, .{}) catch unreachable;
import_records[import_record_i] = import_record_id;
}, loc);

stmt_i += 1;
p.named_imports.put(
p.jsx_refresh_runtime.ref,
js_ast.NamedImport{
.alias = refresh_runtime_symbol.original_name,
.alias_is_star = true,
.alias_loc = loc,
.namespace_ref = p.jsx_refresh_runtime.ref,
.import_record_index = import_record_id,
},
) catch unreachable;
p.is_import_item.put(p.allocator, p.jsx_refresh_runtime.ref, .{}) catch unreachable;
import_records[import_record_i] = import_record_id;
}
p.recordUsage(p.jsx_refresh_runtime.ref);
}

Expand All @@ -2513,46 +2516,48 @@ pub const Parser = struct {

if (!did_import_fast_refresh and p.options.features.react_fast_refresh) {
p.resolveGeneratedSymbol(&p.jsx_refresh_runtime);
p.recordUsage(p.jsx_refresh_runtime.ref);

std.debug.assert(!p.options.enable_bundling);
var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, 1);
const loc = logger.Loc.Empty;
const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime);
p.import_records.items[import_record_id].tag = .react_refresh;

var import_stmt = p.s(S.Import{
.namespace_ref = p.jsx_refresh_runtime.ref,
.star_name_loc = loc,
.is_single_line = true,
.import_record_index = import_record_id,
}, loc);
if (!p.options.jsx.use_embedded_refresh_runtime) {
std.debug.assert(!p.options.enable_bundling);
var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, 1);
const loc = logger.Loc.Empty;
const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime);
p.import_records.items[import_record_id].tag = .react_refresh;

p.recordUsage(p.jsx_refresh_runtime.ref);
const refresh_runtime_symbol: *const Symbol = &p.symbols.items[p.jsx_refresh_runtime.ref.inner_index];

p.named_imports.put(
p.jsx_refresh_runtime.ref,
js_ast.NamedImport{
.alias = refresh_runtime_symbol.original_name,
.alias_is_star = true,
.alias_loc = loc,
var import_stmt = p.s(S.Import{
.namespace_ref = p.jsx_refresh_runtime.ref,
.star_name_loc = loc,
.is_single_line = true,
.import_record_index = import_record_id,
},
) catch unreachable;
p.is_import_item.put(p.allocator, p.jsx_refresh_runtime.ref, .{}) catch unreachable;
var import_records = try p.allocator.alloc(@TypeOf(import_record_id), 1);
import_records[0] = import_record_id;
declared_symbols[0] = .{ .ref = p.jsx_refresh_runtime.ref, .is_top_level = true };
var part_stmts = try p.allocator.alloc(Stmt, 1);
part_stmts[0] = import_stmt;
}, loc);

before.append(js_ast.Part{
.stmts = part_stmts,
.declared_symbols = declared_symbols,
.import_record_indices = import_records,
.symbol_uses = SymbolUseMap{},
}) catch unreachable;
const refresh_runtime_symbol: *const Symbol = &p.symbols.items[p.jsx_refresh_runtime.ref.inner_index];

p.named_imports.put(
p.jsx_refresh_runtime.ref,
js_ast.NamedImport{
.alias = refresh_runtime_symbol.original_name,
.alias_is_star = true,
.alias_loc = loc,
.namespace_ref = p.jsx_refresh_runtime.ref,
.import_record_index = import_record_id,
},
) catch unreachable;
p.is_import_item.put(p.allocator, p.jsx_refresh_runtime.ref, .{}) catch unreachable;
var import_records = try p.allocator.alloc(@TypeOf(import_record_id), 1);
import_records[0] = import_record_id;
declared_symbols[0] = .{ .ref = p.jsx_refresh_runtime.ref, .is_top_level = true };
var part_stmts = try p.allocator.alloc(Stmt, 1);
part_stmts[0] = import_stmt;

before.append(js_ast.Part{
.stmts = part_stmts,
.declared_symbols = declared_symbols,
.import_record_indices = import_records,
.symbol_uses = SymbolUseMap{},
}) catch unreachable;
}
}

if (p.options.enable_bundling) p.resolveBundlingSymbols();
Expand Down Expand Up @@ -3734,7 +3739,13 @@ pub fn NewParser(
if (p.options.features.hot_module_reloading) {
p.hmr_module = try p.declareGeneratedSymbol(.other, "hmr");
if (is_react_fast_refresh_enabled) {
p.jsx_refresh_runtime = try p.declareGeneratedSymbol(.other, "Refresher");
if (p.options.jsx.use_embedded_refresh_runtime) {
p.runtime_imports.__FastRefreshRuntime = try p.declareGeneratedSymbol(.other, "__FastRefreshRuntime");
p.recordUsage(p.runtime_imports.__FastRefreshRuntime.?.ref);
p.jsx_refresh_runtime = p.runtime_imports.__FastRefreshRuntime.?;
} else {
p.jsx_refresh_runtime = try p.declareGeneratedSymbol(.other, "Refresher");
}

p.runtime_imports.__FastRefreshModule = try p.declareGeneratedSymbol(.other, "__FastRefreshModule");
p.recordUsage(p.runtime_imports.__FastRefreshModule.?.ref);
Expand Down Expand Up @@ -3817,7 +3828,11 @@ pub fn NewParser(

pub fn resolveHMRSymbols(p: *P) void {
p.resolveGeneratedSymbol(&p.hmr_module);
if (p.runtime_imports.__FastRefreshModule != null) p.resolveGeneratedSymbol(&p.runtime_imports.__FastRefreshModule.?);
if (p.runtime_imports.__FastRefreshModule != null) {
p.resolveGeneratedSymbol(&p.runtime_imports.__FastRefreshModule.?);
if (p.options.jsx.use_embedded_refresh_runtime)
p.resolveGeneratedSymbol(&p.runtime_imports.__FastRefreshRuntime.?);
}
if (p.runtime_imports.__HMRModule != null) p.resolveGeneratedSymbol(&p.runtime_imports.__HMRModule.?);
if (p.runtime_imports.__HMRClient != null) p.resolveGeneratedSymbol(&p.runtime_imports.__HMRClient.?);
}
Expand Down
80 changes: 77 additions & 3 deletions src/linker.zig
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,18 @@ pub const Linker = struct {
runtime_import_record: ?ImportRecord = null,
hashed_filenames: HashedFileNameMap,
import_counter: usize = 0,
tagged_resolutions: TaggedResolution = TaggedResolution{},

onImportCSS: ?OnImportCallback = null,

pub const runtime_source_path = "bun:wrap";

pub const TaggedResolution = struct {
react_refresh: ?Resolver.Result = null,
jsx_import: ?Resolver.Result = null,
jsx_classic: ?Resolver.Result = null,
};

pub fn init(
allocator: std.mem.Allocator,
log: *logger.Log,
Expand Down Expand Up @@ -212,7 +219,7 @@ pub const Linker = struct {
var record_i: u32 = 0;
const record_count = @truncate(u32, import_records.len);

while (record_i < record_count) : (record_i += 1) {
outer: while (record_i < record_count) : (record_i += 1) {
var import_record = &import_records[record_i];
if (import_record.is_unused) continue;

Expand Down Expand Up @@ -265,8 +272,75 @@ pub const Linker = struct {
}
}

if (linker.resolver.resolve(source_dir, import_record.path.text, import_record.kind)) |*_resolved_import| {
const resolved_import: *const Resolver.Result = _resolved_import;
var resolved_import_ = brk: {
switch (import_record.tag) {
else => {},
.jsx_import => {
if (linker.tagged_resolutions.jsx_import != null) {
break :brk linker.tagged_resolutions.jsx_import.?;
}
},
.jsx_classic => {
if (linker.tagged_resolutions.jsx_classic != null) {
break :brk linker.tagged_resolutions.jsx_classic.?;
}
},
// for fast refresh, attempt to read the version directly from the bundle instead of resolving it
.react_refresh => {
if (linker.options.node_modules_bundle) |node_modules_bundle| {
const runtime = linker.options.jsx.refresh_runtime;
const package_name = runtime[0 .. strings.indexOfChar(runtime, '/') orelse runtime.len];

if (node_modules_bundle.getPackage(package_name)) |pkg| {
const import_path = runtime[@minimum(runtime.len, package_name.len + 1)..];
if (node_modules_bundle.findModuleInPackage(pkg, import_path)) |found_module| {
import_record.is_bundled = true;
node_module_bundle_import_path = node_module_bundle_import_path orelse
linker.nodeModuleBundleImportPath(origin);

import_record.path.text = node_module_bundle_import_path.?;
import_record.module_id = found_module.id;
needs_bundle = true;
continue :outer;
}
}
}

if (linker.options.jsx.use_embedded_refresh_runtime) {
import_record.path = Fs.Path.initWithNamespace(try origin.joinAlloc(linker.allocator, "", "", linker.options.jsx.refresh_runtime, "", ""), "bun");
continue :outer;
}

if (linker.tagged_resolutions.react_refresh != null) {
break :brk linker.tagged_resolutions.react_refresh.?;
}
},
}

if (linker.resolver.resolve(source_dir, import_record.path.text, import_record.kind)) |_resolved_import| {
switch (import_record.tag) {
else => {},
.jsx_import => {
linker.tagged_resolutions.jsx_import = _resolved_import;
linker.tagged_resolutions.jsx_import.?.path_pair.primary = linker.tagged_resolutions.jsx_import.?.path().?.dupeAlloc(_global.default_allocator) catch unreachable;
},
.jsx_classic => {
linker.tagged_resolutions.jsx_classic = _resolved_import;
linker.tagged_resolutions.jsx_classic.?.path_pair.primary = linker.tagged_resolutions.jsx_classic.?.path().?.dupeAlloc(_global.default_allocator) catch unreachable;
},
.react_refresh => {
linker.tagged_resolutions.react_refresh = _resolved_import;
linker.tagged_resolutions.react_refresh.?.path_pair.primary = linker.tagged_resolutions.react_refresh.?.path().?.dupeAlloc(_global.default_allocator) catch unreachable;
},
}

break :brk _resolved_import;
} else |err| {
break :brk err;
}
};

if (resolved_import_) |*resolved_import| {
if (resolved_import.is_external) {
externals.append(record_index) catch unreachable;
continue;
Expand Down
4 changes: 2 additions & 2 deletions src/node_module_bundle.zig
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ pub const NodeModuleBundle = struct {
}

pub fn getPackage(this: *const NodeModuleBundle, name: string) ?*const Api.JavascriptBundledPackage {
const package_id = this.getPackageID(name) orelse return null;
return &this.bundle.packages[@intCast(usize, package_id)];
const package_id = this.getPackageIDByName(name) orelse return null;
return &this.bundle.packages[@intCast(usize, package_id[0])];
}

pub fn hasModule(this: *const NodeModuleBundle, name: string) ?*const Api.JavascriptBundledPackage {
Expand Down
Loading

0 comments on commit 857e9be

Please sign in to comment.