diff --git a/quickjs-libc.c b/quickjs-libc.c
index 6082b7465..37ccca77a 100644
--- a/quickjs-libc.c
+++ b/quickjs-libc.c
@@ -769,6 +769,35 @@ int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val,
     return 0;
 }
 
+static int js_module_loader_json(JSContext *ctx, JSModuleDef *m)
+{
+    size_t buf_len;
+    uint8_t *buf;
+    JSValue parsed;
+    JSPropertyEnum *props;
+    uint32_t len;
+    JSAtom module_name = JS_GetModuleName(ctx, m);
+    const char *module_name_cstr = JS_AtomToCString(ctx, module_name);
+
+    buf = js_load_file(ctx, &buf_len, module_name_cstr);
+
+    /* XXX: Not ideal to parse the file twice, but didn't want to introduce
+       extra state to JSModuleDef like opaque */
+    parsed = JS_ParseJSON(ctx, (const char*) buf, buf_len, module_name_cstr);
+
+    JS_GetOwnPropertyNames(ctx, &props, &len, parsed, JS_GPN_STRING_MASK);
+
+    for (uint32_t i = 0; i < len; i++) {
+        JSValue val = JS_GetProperty(ctx, parsed, props[i].atom);
+        JS_SetModuleExport(ctx, m, JS_AtomToCString(ctx, props[i].atom), val);
+    }
+
+    JS_FreePropertyEnum(ctx, props, len);
+    JS_FreeValue(ctx, parsed);
+    js_free(ctx, buf);
+    return 0;
+}
+
 JSModuleDef *js_module_loader(JSContext *ctx,
                               const char *module_name, void *opaque)
 {
@@ -788,19 +817,70 @@ JSModuleDef *js_module_loader(JSContext *ctx,
             return NULL;
         }
 
-        /* compile the module */
-        func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name,
-                           JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
-        js_free(ctx, buf);
-        if (JS_IsException(func_val))
-            return NULL;
-        if (js_module_set_import_meta(ctx, func_val, true, false) < 0) {
+        JSValueConst with_clause = JS_GetImportAssertion(ctx);
+        bool is_json = false;
+        if (JS_IsArray(with_clause)) {
+            int64_t array_len;
+            JS_GetLength(ctx, with_clause, &array_len);
+            for (int64_t i = 0; i < array_len; i += 2) {
+                JSValue prop = JS_GetPropertyInt64(ctx, with_clause, i);
+                const char *name = JS_ToCString(ctx, prop);
+                if (strcmp(name, "type") == 0) {
+                    JSValue key = JS_GetPropertyInt64(ctx, with_clause, i + 1);
+                    if (!JS_IsString(key)) {
+                        JS_ThrowTypeError(ctx, "value of 'type' is expecting string");
+                        return NULL;
+                    }
+
+                    const char *str = JS_ToCString(ctx, key);
+                    if (strcmp(str, "json") != 0) {
+                        JS_FreeValue(ctx, key);
+                        JS_FreeCString(ctx, str);
+                        JS_ThrowTypeError(ctx, "'type' is not 'json'");
+                        return NULL;
+                    }
+                    JS_FreeValue(ctx, key);
+                    JS_FreeCString(ctx, str);
+                    is_json = true;
+                    break;
+                }
+            }
+        }
+
+        if (is_json) {
+            m = JS_NewCModule(ctx, module_name, js_module_loader_json);
+
+            if (!m)
+                return NULL;
+
+            JSValue parsed = JS_ParseJSON(ctx, (const char*) buf, buf_len, module_name);
+
+            JSPropertyEnum *props;
+            uint32_t len;
+
+            JS_GetOwnPropertyNames(ctx, &props, &len, parsed, JS_GPN_STRING_MASK);
+
+            for (uint32_t i = 0; i < len; i++) {
+                JS_AddModuleExport(ctx, m, JS_AtomToCString(ctx, props[i].atom));
+            }
+
+            JS_FreePropertyEnum(ctx, props, len);
+            JS_FreeValue(ctx, parsed);
+        } else {
+            /* compile the module */
+            func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name,
+                               JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
+            if (JS_IsException(func_val))
+                return NULL;
+            if (js_module_set_import_meta(ctx, func_val, true, false) < 0) {
+                JS_FreeValue(ctx, func_val);
+                return NULL;
+            }
+            /* the module is already referenced, so we must free it */
+            m = JS_VALUE_GET_PTR(func_val);
             JS_FreeValue(ctx, func_val);
-            return NULL;
         }
-        /* the module is already referenced, so we must free it */
-        m = JS_VALUE_GET_PTR(func_val);
-        JS_FreeValue(ctx, func_val);
+        js_free(ctx, buf);
     }
     return m;
 }
diff --git a/quickjs-opcode.h b/quickjs-opcode.h
index bd5be754e..8ea427a46 100644
--- a/quickjs-opcode.h
+++ b/quickjs-opcode.h
@@ -122,7 +122,7 @@ DEF(     apply_eval, 3, 2, 1, u16) /* func array -> ret_eval */
 DEF(         regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a
                                        bytecode string */
 DEF(      get_super, 1, 1, 1, none)
-DEF(         import, 1, 1, 1, none) /* dynamic module import */
+DEF(         import, 1, 2, 1, none) /* dynamic module import */
 
 DEF(      check_var, 5, 0, 1, atom) /* check if a variable exists */
 DEF(  get_var_undef, 5, 0, 1, atom) /* push undefined if the variable does not exist */
diff --git a/quickjs.c b/quickjs.c
index b6277b562..46cd6df05 100644
--- a/quickjs.c
+++ b/quickjs.c
@@ -472,6 +472,10 @@ struct JSContext {
 
     struct list_head loaded_modules; /* list of JSModuleDef.link */
 
+    /* To minimize creating breaking changes, I have instead decided
+       to carry the parsed import_assertion instead */
+    JSValue import_assertion;
+
     /* if NULL, RegExp compilation is not supported */
     JSValue (*compile_regexp)(JSContext *ctx, JSValueConst pattern,
                               JSValueConst flags);
@@ -855,6 +859,9 @@ struct JSModuleDef {
     bool eval_has_exception;
     JSValue eval_exception;
     JSValue meta_obj; /* for import.meta */
+
+    /* a list of key/value strings - [key, value, key, value] */
+    JSValue import_assertion;
 };
 
 typedef struct JSJobEntry {
@@ -1247,7 +1254,7 @@ static void js_free_module_def(JSContext *ctx, JSModuleDef *m);
 static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m,
                                JS_MarkFunc *mark_func);
 static JSValue js_import_meta(JSContext *ctx);
-static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier);
+static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier, JSValueConst import_assertion);
 static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref);
 static JSValue js_new_promise_capability(JSContext *ctx,
                                          JSValue *resolving_funcs,
@@ -2307,6 +2314,7 @@ JSContext *JS_NewContextRaw(JSRuntime *rt)
     ctx->error_back_trace = JS_UNDEFINED;
     ctx->error_prepare_stack = JS_UNDEFINED;
     ctx->error_stack_trace_limit = js_int32(10);
+    ctx->import_assertion = JS_UNDEFINED;
     init_list_head(&ctx->loaded_modules);
 
     JS_AddIntrinsicBasicObjects(ctx);
@@ -2437,6 +2445,7 @@ static void JS_MarkContext(JSRuntime *rt, JSContext *ctx,
     JS_MarkValue(rt, ctx->regexp_ctor, mark_func);
     JS_MarkValue(rt, ctx->function_ctor, mark_func);
     JS_MarkValue(rt, ctx->function_proto, mark_func);
+    JS_MarkValue(rt, ctx->import_assertion, mark_func);
 
     if (ctx->array_shape)
         mark_func(rt, &ctx->array_shape->header);
@@ -2507,6 +2516,7 @@ void JS_FreeContext(JSContext *ctx)
     JS_FreeValue(ctx, ctx->regexp_ctor);
     JS_FreeValue(ctx, ctx->function_ctor);
     JS_FreeValue(ctx, ctx->function_proto);
+    JS_FreeValue(ctx, ctx->import_assertion);
 
     js_free_shape_null(ctx->rt, ctx->array_shape);
 
@@ -17124,10 +17134,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
             {
                 JSValue val;
                 sf->cur_pc = pc;
-                val = js_dynamic_import(ctx, sp[-1]);
+                val = js_dynamic_import(ctx, sp[-2], sp[-1]);
                 if (JS_IsException(val))
                     goto exception;
                 JS_FreeValue(ctx, sp[-1]);
+                JS_FreeValue(ctx, sp[-2]);
                 sp[-1] = val;
             }
             BREAK;
@@ -25150,6 +25161,14 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
                 return js_parse_error(s, "invalid use of 'import()'");
             if (js_parse_assign_expr(s))
                 return -1;
+            if (s->token.val == ',') {
+                if (next_token(s))
+                    return -1;
+                if (js_parse_object_literal(s))
+                    return -1;
+            } else
+                emit_op(s, OP_undefined);
+
             if (js_parse_expect(s, ')'))
                 return -1;
             emit_op(s, OP_import);
@@ -27689,6 +27708,7 @@ static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name)
     m->eval_exception = JS_UNDEFINED;
     m->meta_obj = JS_UNDEFINED;
     m->promise = JS_UNDEFINED;
+    m->import_assertion = JS_UNDEFINED;
     m->resolving_funcs[0] = JS_UNDEFINED;
     m->resolving_funcs[1] = JS_UNDEFINED;
     list_add_tail(&m->link, &ctx->loaded_modules);
@@ -27712,9 +27732,11 @@ static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m,
     JS_MarkValue(rt, m->func_obj, mark_func);
     JS_MarkValue(rt, m->eval_exception, mark_func);
     JS_MarkValue(rt, m->meta_obj, mark_func);
+    JS_MarkValue(rt, m->import_assertion, mark_func);
     JS_MarkValue(rt, m->promise, mark_func);
     JS_MarkValue(rt, m->resolving_funcs[0], mark_func);
     JS_MarkValue(rt, m->resolving_funcs[1], mark_func);
+    JS_MarkValue(rt, m->import_assertion, mark_func);
 }
 
 static void js_free_module_def(JSContext *ctx, JSModuleDef *m)
@@ -27754,6 +27776,7 @@ static void js_free_module_def(JSContext *ctx, JSModuleDef *m)
     JS_FreeValue(ctx, m->promise);
     JS_FreeValue(ctx, m->resolving_funcs[0]);
     JS_FreeValue(ctx, m->resolving_funcs[1]);
+    JS_FreeValue(ctx, m->import_assertion);
     list_del(&m->link);
     js_free(ctx, m);
 }
@@ -28484,6 +28507,11 @@ JSValue JS_GetModuleNamespace(JSContext *ctx, JSModuleDef *m)
     return js_dup(m->module_ns);
 }
 
+JSValueConst JS_GetImportAssertion(JSContext *ctx)
+{
+    return ctx->import_assertion;
+}
+
 #ifdef ENABLE_DUMPS // JS_DUMP_MODULE_RESOLVE
 #define module_trace(ctx, ...) \
    do { \
@@ -28509,6 +28537,8 @@ static int js_resolve_module(JSContext *ctx, JSModuleDef *m)
     }
 #endif
     m->resolved = true;
+
+    ctx->import_assertion = js_dup(m->import_assertion);
     /* resolve each requested module */
     for(i = 0; i < m->req_module_entries_count; i++) {
         JSReqModuleEntry *rme = &m->req_module_entries[i];
@@ -28522,6 +28552,8 @@ static int js_resolve_module(JSContext *ctx, JSModuleDef *m)
         if (js_resolve_module(ctx, m1) < 0)
             return -1;
     }
+    JS_FreeValue(ctx, ctx->import_assertion);
+    ctx->import_assertion = JS_UNDEFINED;
     return 0;
 }
 
@@ -29047,6 +29079,7 @@ static JSValue js_dynamic_import_job(JSContext *ctx,
     JSValueConst *resolving_funcs = argv;
     JSValueConst basename_val = argv[2];
     JSValueConst specifier = argv[3];
+    ctx->import_assertion = unsafe_unconst(argv[4]);
     const char *basename = NULL, *filename;
     JSValue ret, err;
 
@@ -29076,11 +29109,12 @@ static JSValue js_dynamic_import_job(JSContext *ctx,
     return JS_UNDEFINED;
 }
 
-static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier)
+static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier, JSValueConst import_assertion)
 {
     JSAtom basename;
-    JSValue promise, resolving_funcs[2], basename_val;
-    JSValue args[4];
+    JSValue promise, resolving_funcs[2], basename_val, assertion;
+    JSValue args[5];
+    assertion = JS_UNDEFINED;
 
     basename = JS_GetScriptOrModuleName(ctx, 0);
     if (basename == JS_ATOM_NULL)
@@ -29097,14 +29131,51 @@ static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier)
         return promise;
     }
 
+    if (JS_IsObject(import_assertion)) {
+        assertion = JS_NewArray(ctx);
+        if (!JS_HasProperty(ctx, import_assertion, JS_NewAtom(ctx, "with"))) {
+            JS_FreeValue(ctx, promise);
+            JS_FreeValue(ctx, resolving_funcs[0]);
+            JS_FreeValue(ctx, resolving_funcs[1]);
+            JS_FreeValue(ctx, assertion);
+            return JS_ThrowTypeError(ctx, "expected 'with' property");
+        }
+
+        JSValue obj = JS_GetPropertyStr(ctx, import_assertion, "with");
+        if (!JS_IsObject(obj) || JS_IsArray(obj))
+            return JS_ThrowTypeError(ctx, "expected object");
+
+        JSPropertyEnum *props;
+        uint32_t index, len;
+        index = 0;
+
+        JS_GetOwnPropertyNames(ctx, &props, &len, obj, JS_GPN_STRING_MASK);
+
+        for (uint32_t i = 0; i < len; i++) {
+            JSValue key = JS_AtomToString(ctx, props[i].atom);
+            JSValue val = JS_GetProperty(ctx, obj, props[i].atom);
+
+            JS_DefinePropertyValueUint32(ctx, assertion, index,
+                key, JS_PROP_HAS_ENUMERABLE);
+            index++;
+            JS_DefinePropertyValueUint32(ctx, assertion, index,
+                val, JS_PROP_HAS_ENUMERABLE);
+            index++;
+        }
+
+        JS_FreePropertyEnum(ctx, props, len);
+        JS_FreeValue(ctx, obj);
+    }
+
     args[0] = resolving_funcs[0];
     args[1] = resolving_funcs[1];
     args[2] = basename_val;
     args[3] = unsafe_unconst(specifier);
+    args[4] = assertion;
 
     /* cannot run JS_LoadModuleInternal synchronously because it would
        cause an unexpected recursion in js_evaluate_module() */
-    JS_EnqueueJob(ctx, js_dynamic_import_job, 4, vc(args));
+    JS_EnqueueJob(ctx, js_dynamic_import_job, 5, vc(args));
 
     JS_FreeValue(ctx, basename_val);
     JS_FreeValue(ctx, resolving_funcs[0]);
@@ -29737,6 +29808,68 @@ static int add_import(JSParseState *s, JSModuleDef *m,
     return 0;
 }
 
+static __exception int js_parse_import_assertion(JSParseState *s, JSModuleDef *m)
+{
+    uint32_t index = 0;
+    m->import_assertion = JS_NewArray(s->ctx);
+
+    if (next_token(s))
+        return -1;
+
+    if (js_parse_expect(s, '{'))
+        return -1;
+
+    while (s->token.val != '}') {
+        JSValue key, value;
+        key = value = JS_UNDEFINED;
+
+        if (!token_is_ident(s->token.val) && s->token.val != TOK_STRING) {
+            js_parse_error(s, "identifier or string expected");
+            return -1;
+        }
+
+        if (token_is_ident(s->token.val))
+            key = JS_AtomToValue(s->ctx, s->token.u.ident.atom);
+        else
+            key = js_dup(s->token.u.str.str);
+
+        if (next_token(s))
+            goto fail;
+
+        if (js_parse_expect(s, ':'))
+            goto fail;
+
+        if (s->token.val != TOK_STRING) {
+            js_parse_error(s, "string expected");
+            goto fail;
+        }
+
+        value = js_dup(s->token.u.str.str);
+
+        JS_DefinePropertyValueUint32(s->ctx, m->import_assertion, index,
+            key, JS_PROP_HAS_ENUMERABLE);
+        index++;
+        JS_DefinePropertyValueUint32(s->ctx, m->import_assertion, index,
+            value, JS_PROP_HAS_ENUMERABLE);
+        index++;
+
+        if (next_token(s))
+            goto fail;
+
+        continue;
+    fail:
+        JS_FreeValue(s->ctx, m->import_assertion);
+        JS_FreeValue(s->ctx, key);
+        JS_FreeValue(s->ctx, value);
+        return -1;
+    }
+
+    if (next_token(s))
+        return -1;
+
+    return 0;
+}
+
 static __exception int js_parse_import(JSParseState *s)
 {
     JSContext *ctx = s->ctx;
@@ -29841,6 +29974,10 @@ static __exception int js_parse_import(JSParseState *s)
         module_name = js_parse_from_clause(s);
         if (module_name == JS_ATOM_NULL)
             return -1;
+
+        if (s->token.val == TOK_WITH)
+            if (js_parse_import_assertion(s, m) < 0)
+                return -1;
     }
     idx = add_req_module_entry(ctx, m, module_name);
     JS_FreeAtom(ctx, module_name);
diff --git a/quickjs.h b/quickjs.h
index 59c4956fd..7a11cbb2f 100644
--- a/quickjs.h
+++ b/quickjs.h
@@ -1053,6 +1053,7 @@ JS_EXTERN void JS_SetModuleLoaderFunc(JSRuntime *rt,
 JS_EXTERN JSValue JS_GetImportMeta(JSContext *ctx, JSModuleDef *m);
 JS_EXTERN JSAtom JS_GetModuleName(JSContext *ctx, JSModuleDef *m);
 JS_EXTERN JSValue JS_GetModuleNamespace(JSContext *ctx, JSModuleDef *m);
+JS_EXTERN JSValueConst JS_GetImportAssertion(JSContext *ctx);
 
 /* JS Job support */