a bit more, svelte doesnt fully import yet

This commit is contained in:
dave caruso
2024-11-26 20:33:53 -08:00
committed by snwy
parent 150d13868e
commit 4828bef7bd
12 changed files with 311 additions and 131 deletions

View File

@@ -3873,7 +3873,6 @@ declare module "bun" {
* The default loader for this file extension
*/
loader: Loader;
/**
* Defer the execution of this callback until all other modules have been parsed.
*

13
src/bake/bake.d.ts vendored
View File

@@ -492,13 +492,23 @@ declare module "bun" {
app?: Bake.Options | undefined;
}
declare interface Plug {
declare interface PluginBuilder {
/**
* Inject a module into the development server's runtime, to be loaded
* before all other user code.
*/
addPreload(module: string, side: 'client' | 'server'): void;
}
declare interface OnLoadArgs {
/**
* When using server-components, the same bundle has both client and server
* files; A single plugin can operate on files from both module graphs.
* Outside of server-components, this will be "client" when the target is
* set to "browser" and "server" otherwise.
*/
side: 'server' | 'client';
}
}
/** Available in server-side files only. */
@@ -557,6 +567,7 @@ declare module "bun:bake/server" {
};
};
}
}
/** Available in client-side files. */

View File

@@ -380,11 +380,14 @@ pub const Framework = struct {
return global.throwInvalidArguments("Missing 'framework.serverComponents.serverRuntimeImportSource'", .{});
},
),
.server_register_client_reference = refs.track(
try sc.getOptional(global, "serverRegisterClientReferenceExport", ZigString.Slice) orelse {
return global.throwInvalidArguments("Missing 'framework.serverComponents.serverRegisterClientReferenceExport'", .{});
},
),
.server_register_client_reference = if (try sc.getOptional(
global,
"serverRegisterClientReferenceExport",
ZigString.Slice,
)) |slice|
refs.track(slice)
else
"registerClientReference",
};
};
const built_in_modules: bun.StringArrayHashMapUnmanaged(BuiltInModule) = built_in_modules: {
@@ -456,9 +459,9 @@ pub const Framework = struct {
break :exts &.{};
}
} else if (exts_js.isArray()) {
var it_2 = array.arrayIterator(global);
var it_2 = exts_js.arrayIterator(global);
var i_2: usize = 0;
const extensions = try arena.alloc([]const u8, len);
const extensions = try arena.alloc([]const u8, array.getLength(global));
while (it_2.next()) |array_item| : (i_2 += 1) {
const slice = refs.track(try array_item.toSlice2(global, arena));
if (bun.strings.eqlComptime(slice, "*"))

View File

@@ -39,6 +39,7 @@ export class HotModule<E = any> {
_cached_failure: any = undefined;
// modules that import THIS module
_deps: Map<HotModule, DepEntry | undefined> = new Map();
_onDispose: HotDisposeFunction[] | undefined = undefined;
constructor(id: Id) {
this.id = id;
@@ -55,13 +56,14 @@ export class HotModule<E = any> {
mod._deps.set(this, onReload ? { _callback: onReload, _expectedImports: expectedImports } : undefined);
const { exports, __esModule } = mod;
const object = __esModule ? exports : (mod._ext_exports ??= { ...exports, default: exports });
if (expectedImports) {
for (const key of expectedImports) {
if (!(key in object)) {
throw new SyntaxError(`The requested module '${id}' does not provide an export named '${key}'`);
}
}
}
// TODO: restore this validation. not correct due to import cycles which are allowed usually
// if (expectedImports) {
// for (const key of expectedImports) {
// if (!(key in object)) {
// throw new SyntaxError(`The requested module '${id}' does not provide an export named '${key}'`);
// }
// }
// }
return object;
}
@@ -93,14 +95,73 @@ function initImportMeta(m: HotModule): ImportMeta {
url: `bun://${m.id}`,
main: false,
// @ts-ignore
hot: {
accept() {
throw new Error("Not implemented");
},
}
get hot() {
const hot = new Hot(m);
Object.defineProperty(this, "hot", { value: hot });
return hot;
},
};
}
type HotAcceptFunction = (esmExports: any | void) => void;
type HotArrayAcceptFunction = (esmExports: (any | void)[]) => void;
type HotDisposeFunction = (data: any) => void;
type HotEventHandler = (data: any) => void;
class Hot {
private _module: HotModule;
constructor(module: HotModule) {
this._module = module;
}
accept(
arg1: string | readonly string[] | HotAcceptFunction,
arg2: HotAcceptFunction | HotArrayAcceptFunction | undefined,
) {
console.warn("TODO: implement ImportMetaHot.accept (called from " + JSON.stringify(this._module.id) + ")");
}
dispose(cb: HotDisposeFunction) {
(this._module._onDispose ??= []).push(cb);
}
prune(cb: HotDisposeFunction) {
throw new Error("TODO: implement ImportMetaHot.prune");
}
invalidate() {
throw new Error("TODO: implement ImportMetaHot.invalidate");
}
on(event: string, cb: HotEventHandler) {
if (isUnsupportedViteEventName(event)) {
throw new Error(`Unsupported event name: ${event}`);
}
throw new Error("TODO: implement ImportMetaHot.on");
}
off(event: string, cb: HotEventHandler) {
throw new Error("TODO: implement ImportMetaHot.off");
}
send(event: string, cb: HotEventHandler) {
throw new Error("TODO: implement ImportMetaHot.send");
}
}
function isUnsupportedViteEventName(str: string) {
return str === 'vite:beforeUpdate'
|| str === 'vite:afterUpdate'
|| str === 'vite:beforeFullReload'
|| str === 'vite:beforePrune'
|| str === 'vite:invalidate'
|| str === 'vite:error'
|| str === 'vite:ws:disconnect'
|| str === 'vite:ws:connect';
}
/**
* Load a module by ID. Use `type` to specify if the module is supposed to be
* present, or is something a user is able to dynamically specify.

View File

@@ -771,6 +771,7 @@ pub const JSBundler = struct {
load.namespace,
load,
load.default_loader,
load.bakeGraph() != .client,
);
}
@@ -881,6 +882,7 @@ pub const JSBundler = struct {
path: *const String,
context: *anyopaque,
u8,
bool,
) void;
extern fn JSBundlerPlugin__matchOnResolve(
@@ -921,6 +923,7 @@ pub const JSBundler = struct {
namespace: []const u8,
context: *anyopaque,
default_loader: options.Loader,
is_server_side: bool,
) void {
JSC.markBinding(@src());
const tracer = bun.tracy.traceNamed(@src(), "JSBundler.matchOnLoad");
@@ -933,7 +936,7 @@ pub const JSBundler = struct {
const path_string = bun.String.createUTF8(path);
defer namespace_string.deref();
defer path_string.deref();
JSBundlerPlugin__matchOnLoad(this, &namespace_string, &path_string, context, @intFromEnum(default_loader));
JSBundlerPlugin__matchOnLoad(this, &namespace_string, &path_string, context, @intFromEnum(default_loader), is_server_side);
}
pub fn matchOnResolve(

View File

@@ -325,7 +325,7 @@ extern "C" bool JSBundlerPlugin__anyMatches(Bun::JSBundlerPlugin* pluginObject,
return pluginObject->plugin.anyMatchesCrossThread(pluginObject->vm(), namespaceString, path, isOnLoad);
}
extern "C" void JSBundlerPlugin__matchOnLoad(Bun::JSBundlerPlugin* plugin, const BunString* namespaceString, const BunString* path, void* context, uint8_t defaultLoaderId)
extern "C" void JSBundlerPlugin__matchOnLoad(Bun::JSBundlerPlugin* plugin, const BunString* namespaceString, const BunString* path, void* context, uint8_t defaultLoaderId, bool isServerSide)
{
JSC::JSGlobalObject *globalObject = plugin->globalObject();
WTF::String namespaceStringStr = namespaceString ? namespaceString->toWTFString(BunString::ZeroCopy) : WTF::String();
@@ -346,6 +346,7 @@ extern "C" void JSBundlerPlugin__matchOnLoad(Bun::JSBundlerPlugin* plugin, const
arguments.append(JSC::jsString(plugin->vm(), pathStr));
arguments.append(JSC::jsString(plugin->vm(), namespaceStringStr));
arguments.append(JSC::jsNumber(defaultLoaderId));
arguments.append(JSC::jsBoolean(isServerSide));
call(globalObject, function, callData, plugin, arguments);

View File

@@ -2412,7 +2412,8 @@ pub const ModuleLoader = struct {
else
specifier[@min(namespace.len + 1, specifier.len)..];
return globalObject.runOnLoadPlugins(bun.String.init(namespace), bun.String.init(after_namespace), .bun) orelse return JSValue.zero;
return globalObject.runOnLoadPlugins(bun.String.init(namespace), bun.String.init(after_namespace), .bun) orelse
return JSValue.zero;
}
pub fn fetchBuiltinModule(jsc_vm: *VirtualMachine, specifier: bun.String) !?ResolvedSource {

View File

@@ -22,7 +22,7 @@ interface BundlerPlugin {
/** Binding to `JSBundlerPlugin__addError` */
addError(internalID: number, error: any, which: number): void;
addFilter(filter, namespace, number): void;
generateDeferPromise(): Promise<void>;
generateDeferPromise(id: number): Promise<void>;
promises: Array<Promise<any>> | undefined;
}
@@ -335,12 +335,12 @@ export function runOnResolvePlugins(this: BundlerPlugin, specifier, inputNamespa
}
}
export function runOnLoadPlugins(this: BundlerPlugin, internalID, path, namespace, defaultLoaderId) {
export function runOnLoadPlugins(this: BundlerPlugin, internalID, path, namespace, defaultLoaderId, isServerSide: boolean) {
const LOADERS_MAP = $LoaderLabelToId;
const loaderName = $LoaderIdToLabel[defaultLoaderId];
const generateDefer = () => this.generateDeferPromise(internalID);
var promiseResult = (async (internalID, path, namespace, defaultLoader, generateDefer) => {
var promiseResult = (async (internalID, path, namespace, isServerSide, defaultLoader, generateDefer) => {
var results = this.onLoad.$get(namespace);
if (!results) {
this.onLoadAsync(internalID, null, null);
@@ -356,6 +356,7 @@ export function runOnLoadPlugins(this: BundlerPlugin, internalID, path, namespac
// pluginData
loader: defaultLoader,
defer: generateDefer,
side: isServerSide ? "server" : "client",
});
while (
@@ -407,7 +408,7 @@ export function runOnLoadPlugins(this: BundlerPlugin, internalID, path, namespac
this.onLoadAsync(internalID, null, null);
return null;
})(internalID, path, namespace, loaderName, generateDefer);
})(internalID, path, namespace, isServerSide, loaderName, generateDefer);
while (
promiseResult &&

View File

@@ -8986,64 +8986,62 @@ fn NewParser_(
try p.is_import_item.ensureUnusedCapacity(p.allocator, count_excluding_namespace);
var remap_count: u32 = 0;
// Link the default item to the namespace
if (stmt.default_name) |*name_loc| {
outer: {
const name = p.loadNameFromRef(name_loc.ref.?);
const ref = try p.declareSymbol(.import, name_loc.loc, name);
name_loc.ref = ref;
try p.is_import_item.put(p.allocator, ref, {});
if (stmt.default_name) |*name_loc| outer: {
const name = p.loadNameFromRef(name_loc.ref.?);
const ref = try p.declareSymbol(.import, name_loc.loc, name);
name_loc.ref = ref;
try p.is_import_item.put(p.allocator, ref, {});
// ensure every e_import_identifier holds the namespace
if (p.options.features.hot_module_reloading) {
const symbol = &p.symbols.items[ref.inner_index];
if (symbol.namespace_alias == null) {
symbol.namespace_alias = .{
.namespace_ref = stmt.namespace_ref,
.alias = "default",
.import_record_index = stmt.import_record_index,
};
}
}
if (macro_remap) |*remap| {
if (remap.get("default")) |remapped_path| {
const new_import_id = p.addImportRecord(.stmt, path.loc, remapped_path);
try p.macro.refs.put(ref, new_import_id);
p.import_records.items[new_import_id].path.namespace = js_ast.Macro.namespace;
p.import_records.items[new_import_id].is_unused = true;
if (comptime only_scan_imports_and_do_not_visit) {
p.import_records.items[new_import_id].is_internal = true;
p.import_records.items[new_import_id].path.is_disabled = true;
}
stmt.default_name = null;
remap_count += 1;
break :outer;
}
}
if (comptime track_symbol_usage_during_parse_pass) {
p.parse_pass_symbol_uses.put(name, .{
.ref = ref,
// ensure every e_import_identifier holds the namespace
if (p.options.features.hot_module_reloading) {
const symbol = &p.symbols.items[ref.inner_index];
if (symbol.namespace_alias == null) {
symbol.namespace_alias = .{
.namespace_ref = stmt.namespace_ref,
.alias = "default",
.import_record_index = stmt.import_record_index,
}) catch unreachable;
};
}
}
if (is_macro) {
try p.macro.refs.put(ref, stmt.import_record_index);
if (macro_remap) |*remap| {
if (remap.get("default")) |remapped_path| {
const new_import_id = p.addImportRecord(.stmt, path.loc, remapped_path);
try p.macro.refs.put(ref, new_import_id);
p.import_records.items[new_import_id].path.namespace = js_ast.Macro.namespace;
p.import_records.items[new_import_id].is_unused = true;
if (comptime only_scan_imports_and_do_not_visit) {
p.import_records.items[new_import_id].is_internal = true;
p.import_records.items[new_import_id].path.is_disabled = true;
}
stmt.default_name = null;
remap_count += 1;
break :outer;
}
if (comptime ParsePassSymbolUsageType != void) {
p.parse_pass_symbol_uses.put(name, .{
.ref = ref,
.import_record_index = stmt.import_record_index,
}) catch unreachable;
}
item_refs.putAssumeCapacity(name, name_loc.*);
}
if (comptime track_symbol_usage_during_parse_pass) {
p.parse_pass_symbol_uses.put(name, .{
.ref = ref,
.import_record_index = stmt.import_record_index,
}) catch unreachable;
}
if (is_macro) {
try p.macro.refs.put(ref, stmt.import_record_index);
stmt.default_name = null;
break :outer;
}
if (comptime ParsePassSymbolUsageType != void) {
p.parse_pass_symbol_uses.put(name, .{
.ref = ref,
.import_record_index = stmt.import_record_index,
}) catch unreachable;
}
item_refs.putAssumeCapacity(name, name_loc.*);
}
var end: usize = 0;
@@ -12469,7 +12467,6 @@ fn NewParser_(
fn declareSymbolMaybeGenerated(p: *P, kind: Symbol.Kind, loc: logger.Loc, name: string, comptime is_generated: bool) !Ref {
// p.checkForNonBMPCodePoint(loc, name)
if (comptime !is_generated) {
// Forbid declaring a symbol with a reserved word in strict mode
if (p.isStrictMode() and name.ptr != arguments_str.ptr and js_lexer.StrictModeReservedWords.has(name)) {
@@ -19046,16 +19043,6 @@ fn NewParser_(
}
},
.s_export_from => |data| {
// When HMR is enabled, we need to transform this into
// import {foo} from "./foo";
// export {foo};
// From:
// export {foo as default} from './foo';
// To:
// import {default as foo} from './foo';
// export {foo};
// "export {foo} from 'path'"
const name = p.loadNameFromRef(data.namespace_ref);
@@ -19079,7 +19066,7 @@ fn NewParser_(
const _name = p.loadNameFromRef(old_ref);
const ref = try p.newSymbol(.other, _name);
const ref = try p.newSymbol(.import, _name);
try p.current_scope.generated.push(p.allocator, ref);
try p.recordDeclaredSymbol(ref);
data.items[j] = item;
@@ -19093,11 +19080,10 @@ fn NewParser_(
return;
}
} else {
// This is a re-export and the symbols created here are used to reference
for (data.items) |*item| {
const _name = p.loadNameFromRef(item.name.ref.?);
const ref = try p.newSymbol(.other, _name);
const ref = try p.newSymbol(.import, _name);
try p.current_scope.generated.push(p.allocator, ref);
try p.recordDeclaredSymbol(ref);
item.name.ref = ref;
@@ -23961,19 +23947,37 @@ pub const ConvertESMExportsForHmr = struct {
},
.s_export_clause => |st| {
for (st.items) |item| {
try ctx.export_props.append(p.allocator, .{
.key = Expr.init(E.String, .{
.data = item.alias,
}, stmt.loc),
.value = Expr.initIdentifier(item.name.ref.?, item.name.loc),
});
const ref = item.name.ref.?;
try ctx.visitRefForBakeModuleExports(p, ref, item.name.loc, false);
}
return; // do not emit a statement here
},
.s_export_from => {
bun.todoPanic(@src(), "hot-module-reloading instrumentation for 'export {{ ... }} from'", .{});
.s_export_from => |st| stmt: {
for (st.items) |item| {
const ref = item.name.ref.?;
const symbol = &p.symbols.items[ref.innerIndex()];
if (symbol.namespace_alias == null) {
symbol.namespace_alias = .{
.namespace_ref = st.namespace_ref,
.alias = item.original_name,
.import_record_index = st.import_record_index,
};
}
try ctx.visitRefForBakeModuleExports(p, ref, item.name.loc, true);
}
const gop = try ctx.imports_seen.getOrPut(p.allocator, st.import_record_index);
if (gop.found_existing) return;
break :stmt Stmt.alloc(S.Import, .{
.import_record_index = st.import_record_index,
.is_single_line = true,
.default_name = null,
.items = st.items,
.namespace_ref = st.namespace_ref,
.star_name_loc = null,
}, stmt.loc);
},
.s_export_star => {
bun.todoPanic(@src(), "hot-module-reloading instrumentation for 'export * from'", .{});
@@ -24001,7 +24005,7 @@ pub const ConvertESMExportsForHmr = struct {
switch (binding.data) {
.b_missing => {},
.b_identifier => |id| {
try ctx.visitRefForKitModuleExports(p, id.ref, binding.loc, is_live_binding);
try ctx.visitRefForBakeModuleExports(p, id.ref, binding.loc, is_live_binding);
},
.b_array => |array| {
for (array.items) |item| {
@@ -24016,16 +24020,24 @@ pub const ConvertESMExportsForHmr = struct {
}
}
fn visitRefForKitModuleExports(
fn visitRefForBakeModuleExports(
ctx: *ConvertESMExportsForHmr,
p: anytype,
ref: Ref,
loc: logger.Loc,
is_live_binding: bool,
is_live_binding_source: bool,
) !void {
const symbol = p.symbols.items[ref.inner_index];
const id = Expr.initIdentifier(ref, loc);
if (is_live_binding) {
std.debug.print("yolo {s}, {s}\n", .{ symbol.original_name, @tagName(symbol.kind) });
const id = if (symbol.kind == .import)
Expr.init(E.ImportIdentifier, .{ .ref = ref }, loc)
else
Expr.initIdentifier(ref, loc);
if (is_live_binding_source or symbol.kind == .import) {
// TODO: instead of requiring getters for live-bindings,
// a callback propagation system should be considered.
// mostly because here, these might not even be live
// bindings, and re-exports are so, so common.
const key = Expr.init(E.String, .{
.data = symbol.original_name,
}, loc);
@@ -24052,24 +24064,7 @@ pub const ConvertESMExportsForHmr = struct {
},
} }, loc),
});
// 'set abc(abc2) { abc = abc2 }'
try ctx.export_props.append(p.allocator, .{
.kind = .set,
.key = key,
.value = Expr.init(E.Function, .{ .func = .{
.args = try p.allocator.dupe(G.Arg, &.{.{
.binding = Binding.alloc(p.allocator, B.Identifier{ .ref = arg1 }, loc),
}}),
.body = .{
.stmts = try p.allocator.dupe(Stmt, &.{
Stmt.alloc(S.SExpr, .{
.value = Expr.assign(id, Expr.initIdentifier(arg1, loc)),
}, loc),
}),
.loc = loc,
},
} }, loc),
});
// no setter is added since live bindings are read-only
} else {
// 'abc,'
try ctx.export_props.append(p.allocator, .{

View File

@@ -293,7 +293,7 @@ class OutputLineStream extends EventEmitter {
}
}
export function devTest(description: string, options: DevServerTest) {
export function devTest<T extends DevServerTest>(description: string, options: T): T {
// Capture the caller name as part of the test tempdir
const callerLocation = snapshotCallerLocation();
const caller = stackTraceFileName(callerLocation);
@@ -305,7 +305,7 @@ export function devTest(description: string, options: DevServerTest) {
// TODO: Tests are too flaky on Windows. Cannot reproduce locally.
if (isWindows) {
jest.test.todo(`DevServer > ${basename}.${count}: ${description}`);
return;
return options;
}
jest.test(`DevServer > ${basename}.${count}: ${description}`, async () => {
@@ -377,4 +377,5 @@ export function devTest(description: string, options: DevServerTest) {
throw err;
}
});
return options;
}

View File

@@ -43,8 +43,7 @@ devTest("onLoad", {
setup(build) {
let a = 0;
build.onLoad({ filter: /trigger/ }, (args) => {
a += 1;
return { contents: 'export const value = ' + a + ';', loader: 'ts' };
return { contents: 'export const value = 1;', loader: 'ts' };
});
},
}
@@ -65,8 +64,43 @@ devTest("onLoad", {
async test(dev) {
await dev.fetch("/").expect('value: 1');
await dev.fetch("/").expect('value: 1');
await dev.write("trigger.ts", "throw new Error('should not be loaded 2');");
await dev.fetch("/").expect('value: 2');
await dev.fetch("/").expect('value: 2');
await dev.fetch("/").expect('value: 1');
},
});
// devTest("onLoad with watchFile", {
// framework: minimalFramework,
// pluginFile: `
// import * as path from 'path';
// export default [
// {
// name: 'a',
// setup(build) {
// let a = 0;
// build.onLoad({ filter: /trigger/ }, (args) => {
// a += 1;
// return { contents: 'export const value = ' + a + ';', loader: 'ts' };
// });
// },
// }
// ];
// `,
// files: {
// "trigger.ts": `
// throw new Error('should not be loaded');
// `,
// "routes/index.ts": `
// import { value } from '../trigger.ts';
// export default function (req, meta) {
// return new Response('value: ' + value);
// }
// `,
// },
// async test(dev) {
// await dev.fetch("/").expect('value: 1');
// await dev.fetch("/").expect('value: 1');
// await dev.write("trigger.ts", "throw new Error('should not be loaded 2');");
// await dev.fetch("/").expect('value: 2');
// await dev.fetch("/").expect('value: 2');
// },
// });

View File

@@ -1,7 +1,7 @@
// Bundle tests are tests concerning bundling bugs that only occur in DevServer.
import { devTest, minimalFramework, Step } from "../dev-server-harness";
devTest("live bindings with `var`", {
const liveBindingTest = devTest("live bindings with `var`", {
framework: minimalFramework,
files: {
"state.ts": `
@@ -41,3 +41,73 @@ devTest("live bindings with `var`", {
await dev.fetch("/").expect("Value: -2");
},
});
devTest("live bindings through export clause", {
framework: minimalFramework,
files: {
"state.ts": `
export var value = 0;
export function increment() {
value++;
}
`,
"proxy.ts": `
import { value } from './state';
export { value as live };
`,
"routes/index.ts": `
import { increment } from '../state';
import { live } from '../proxy';
export default function(req, meta) {
increment();
return new Response('State: ' + live);
}
`,
},
test: liveBindingTest.test,
});
devTest("live bindings through export from", {
framework: minimalFramework,
files: {
"state.ts": `
export var value = 0;
export function increment() {
value++;
}
`,
"proxy.ts": `
export { value as live } from './state';
`,
"routes/index.ts": `
import { increment } from '../state';
import { live } from '../proxy';
export default function(req, meta) {
increment();
return new Response('State: ' + live);
}
`,
},
test: liveBindingTest.test,
});
devTest("live bindings through export star", {
framework: minimalFramework,
files: {
"state.ts": `
export var value = 0;
export function increment() {
value++;
}
`,
"proxy.ts": `
export * from './state';
`,
"routes/index.ts": `
import { increment } from '../state';
import { live } from '../proxy';
export default function(req, meta) {
increment();
return new Response('State: ' + live);
}
`,
},
test: liveBindingTest.test,
});