diff --git a/crates/cli/src/init.rs b/crates/cli/src/init.rs
index 8e9cad06..34318368 100644
--- a/crates/cli/src/init.rs
+++ b/crates/cli/src/init.rs
@@ -297,7 +297,7 @@ pub fn generate_grammar_files(
)
},
|path| {
- let contents = fs::read_to_string(path)?
+ let mut contents = fs::read_to_string(path)?
.replace(
r#""node-addon-api": "^8.3.1"#,
r#""node-addon-api": "^8.5.0""#,
@@ -311,6 +311,16 @@ pub fn generate_grammar_files(
"tree-sitter": "^0.22.4",
"tree-sitter-cli":"#},
);
+ if !contents.contains("module") {
+ eprintln!("Updating package.json");
+ contents = contents.replace(
+ indoc! {r#"
+ "repository": {"#},
+ indoc! {r#"
+ "type": "module",
+ "repository": {"#},
+ );
+ }
write_file(path, contents)?;
Ok(())
},
@@ -318,9 +328,20 @@ pub fn generate_grammar_files(
// Do not create a grammar.js file in a repo with multiple language configs
if !tree_sitter_config.has_multiple_language_configs() {
- missing_path(repo_path.join("grammar.js"), |path| {
- generate_file(path, GRAMMAR_JS_TEMPLATE, language_name, &generate_opts)
- })?;
+ missing_path_else(
+ repo_path.join("grammar.js"),
+ allow_update,
+ |path| generate_file(path, GRAMMAR_JS_TEMPLATE, language_name, &generate_opts),
+ |path| {
+ let mut contents = fs::read_to_string(path)?;
+ if contents.contains("module.exports") {
+ contents = contents.replace("module.exports =", "export default");
+ write_file(path, contents)?;
+ }
+
+ Ok(())
+ },
+ )?;
}
// Write .gitignore file
@@ -410,7 +431,7 @@ pub fn generate_grammar_files(
|path| generate_file(path, INDEX_JS_TEMPLATE, language_name, &generate_opts),
|path| {
let contents = fs::read_to_string(path)?;
- if !contents.contains("bun") {
+ if !contents.contains("new URL") {
eprintln!("Replacing index.js");
generate_file(path, INDEX_JS_TEMPLATE, language_name, &generate_opts)?;
}
@@ -422,14 +443,31 @@ pub fn generate_grammar_files(
generate_file(path, INDEX_D_TS_TEMPLATE, language_name, &generate_opts)
})?;
- missing_path(path.join("binding_test.js"), |path| {
- generate_file(
- path,
- BINDING_TEST_JS_TEMPLATE,
- language_name,
- &generate_opts,
- )
- })?;
+ missing_path_else(
+ path.join("binding_test.js"),
+ allow_update,
+ |path| {
+ generate_file(
+ path,
+ BINDING_TEST_JS_TEMPLATE,
+ language_name,
+ &generate_opts,
+ )
+ },
+ |path| {
+ let contents = fs::read_to_string(path)?;
+ if !contents.contains("import") {
+ eprintln!("Replacing binding_test.js");
+ generate_file(
+ path,
+ BINDING_TEST_JS_TEMPLATE,
+ language_name,
+ &generate_opts,
+ )?;
+ }
+ Ok(())
+ },
+ )?;
missing_path(path.join("binding.cc"), |path| {
generate_file(path, JS_BINDING_CC_TEMPLATE, language_name, &generate_opts)
diff --git a/crates/cli/src/templates/binding_test.js b/crates/cli/src/templates/binding_test.js
index 55becacf..e424b266 100644
--- a/crates/cli/src/templates/binding_test.js
+++ b/crates/cli/src/templates/binding_test.js
@@ -1,9 +1,9 @@
-const assert = require("node:assert");
-const { test } = require("node:test");
-
-const Parser = require("tree-sitter");
+import assert from "node:assert";
+import { test } from "node:test";
+import Parser from "tree-sitter";
+import language from "./index.js";
test("can load grammar", () => {
const parser = new Parser();
- assert.doesNotThrow(() => parser.setLanguage(require(".")));
+ assert.doesNotThrow(() => parser.setLanguage(language));
});
diff --git a/crates/cli/src/templates/grammar.js b/crates/cli/src/templates/grammar.js
index 01586557..edee3cbc 100644
--- a/crates/cli/src/templates/grammar.js
+++ b/crates/cli/src/templates/grammar.js
@@ -7,7 +7,7 @@
///
// @ts-check
-module.exports = grammar({
+export default grammar({
name: "LOWER_PARSER_NAME",
rules: {
diff --git a/crates/cli/src/templates/index.js b/crates/cli/src/templates/index.js
index cbbaa32f..2782b741 100644
--- a/crates/cli/src/templates/index.js
+++ b/crates/cli/src/templates/index.js
@@ -1,11 +1,15 @@
-const root = require("path").join(__dirname, "..", "..");
+const root = new URL("../..", import.meta.url).pathname;
-module.exports =
- typeof process.versions.bun === "string"
- // Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time
- ? require(`../../prebuilds/${process.platform}-${process.arch}/tree-sitter-KEBAB_PARSER_NAME.node`)
- : require("node-gyp-build")(root);
+const binding = typeof process.versions.bun === "string"
+ // Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time
+ ? await import(`../../prebuilds/${process.platform}-${process.arch}/tree-sitter-KEBAB_PARSER_NAME.node`)
+ : await import("node-gyp-build");
+
+const result = binding.default ? binding.default(root) : binding(root);
try {
- module.exports.nodeTypeInfo = require("../../src/node-types.json");
+ const nodeTypeInfo = await import("../../src/node-types.json", {assert: {type: "json"}});
+ result.nodeTypeInfo = nodeTypeInfo.default;
} catch (_) {}
+
+export default result;
diff --git a/crates/cli/src/templates/package.json b/crates/cli/src/templates/package.json
index 93dc5854..95f5b638 100644
--- a/crates/cli/src/templates/package.json
+++ b/crates/cli/src/templates/package.json
@@ -2,6 +2,7 @@
"name": "tree-sitter-PARSER_NAME",
"version": "PARSER_VERSION",
"description": "PARSER_DESCRIPTION",
+ "type": "module",
"repository": {
"type": "git",
"url": "git+PARSER_URL.git"
diff --git a/crates/generate/src/generate.rs b/crates/generate/src/generate.rs
index 4583ab9a..800a0319 100644
--- a/crates/generate/src/generate.rs
+++ b/crates/generate/src/generate.rs
@@ -428,6 +428,15 @@ pub fn load_grammar_file(
fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResult {
let grammar_path = fs::canonicalize(grammar_path)?;
+ let grammar_uses_commonjs = fs::read_to_string(&grammar_path)?.contains("module.exports");
+ if grammar_uses_commonjs {
+ eprintln!("Warning: Your grammar.js uses CommonJS.");
+ eprintln!("Consider migrating to ES modules (export default) for better compatibility.");
+ eprintln!(
+ "See: https://tree-sitter.github.io/tree-sitter/creating-parsers/#the-grammar-file"
+ );
+ }
+
#[cfg(windows)]
let grammar_path = url::Url::from_file_path(grammar_path)
.expect("Failed to convert path to URL")
diff --git a/docs/src/creating-parsers/1-getting-started.md b/docs/src/creating-parsers/1-getting-started.md
index f452a85b..92159f4a 100644
--- a/docs/src/creating-parsers/1-getting-started.md
+++ b/docs/src/creating-parsers/1-getting-started.md
@@ -64,7 +64,7 @@ There should be a file called `grammar.js` with the following contents:
///
// @ts-check
-module.exports = grammar({
+export default grammar({
name: 'LOWER_PARSER_NAME',
rules: {
diff --git a/docs/src/creating-parsers/3-writing-the-grammar.md b/docs/src/creating-parsers/3-writing-the-grammar.md
index cb6ec3d4..ebd2f5a4 100644
--- a/docs/src/creating-parsers/3-writing-the-grammar.md
+++ b/docs/src/creating-parsers/3-writing-the-grammar.md
@@ -313,7 +313,7 @@ A construct like `[x, y]` could be legitimately parsed as both an array literal
pattern (like in `let [x, y] = arr`).
```js
-module.exports = grammar({
+export default grammar({
name: "javascript",
rules: {
diff --git a/test/fixtures/test_grammars/aliased_inlined_rules/grammar.js b/test/fixtures/test_grammars/aliased_inlined_rules/grammar.js
index 2f1091e7..9d41fef2 100644
--- a/test/fixtures/test_grammars/aliased_inlined_rules/grammar.js
+++ b/test/fixtures/test_grammars/aliased_inlined_rules/grammar.js
@@ -2,7 +2,7 @@
// shows that you can alias a rule that would otherwise be anonymous, and it will then appear as a
// named node.
-module.exports = grammar({
+export default grammar({
name: 'aliased_inlined_rules',
extras: $ => [/\s/],
diff --git a/test/fixtures/test_grammars/aliased_rules/grammar.js b/test/fixtures/test_grammars/aliased_rules/grammar.js
index a615a90d..df721d5b 100644
--- a/test/fixtures/test_grammars/aliased_rules/grammar.js
+++ b/test/fixtures/test_grammars/aliased_rules/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'aliased_rules',
extras: $ => [
diff --git a/test/fixtures/test_grammars/aliased_token_rules/grammar.js b/test/fixtures/test_grammars/aliased_token_rules/grammar.js
index 704a2a34..2a6f4be3 100644
--- a/test/fixtures/test_grammars/aliased_token_rules/grammar.js
+++ b/test/fixtures/test_grammars/aliased_token_rules/grammar.js
@@ -1,7 +1,7 @@
// This grammar shows that `ALIAS` rules can be applied directly to `TOKEN` and `IMMEDIATE_TOKEN`
// rules.
-module.exports = grammar({
+export default grammar({
name: 'aliased_token_rules',
extras: $ => [/\s/],
diff --git a/test/fixtures/test_grammars/aliased_unit_reductions/grammar.js b/test/fixtures/test_grammars/aliased_unit_reductions/grammar.js
index 9b39de28..77186d07 100644
--- a/test/fixtures/test_grammars/aliased_unit_reductions/grammar.js
+++ b/test/fixtures/test_grammars/aliased_unit_reductions/grammar.js
@@ -5,7 +5,7 @@
// their parent rule. In that situation, eliminating the invisible node could cause the alias to be
// incorrectly applied to its child.
-module.exports = grammar({
+export default grammar({
name: 'aliased_unit_reductions',
extras: $ => [/\s/],
diff --git a/test/fixtures/test_grammars/aliases_in_root/grammar.js b/test/fixtures/test_grammars/aliases_in_root/grammar.js
index 02d61646..9d46af2f 100644
--- a/test/fixtures/test_grammars/aliases_in_root/grammar.js
+++ b/test/fixtures/test_grammars/aliases_in_root/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'aliases_in_root',
extras: $ => [
diff --git a/test/fixtures/test_grammars/anonymous_error/grammar.js b/test/fixtures/test_grammars/anonymous_error/grammar.js
index c06d1bd2..72870612 100644
--- a/test/fixtures/test_grammars/anonymous_error/grammar.js
+++ b/test/fixtures/test_grammars/anonymous_error/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'anonymous_error',
rules: {
document: $ => repeat(choice('ok', 'ERROR')),
diff --git a/test/fixtures/test_grammars/anonymous_tokens_with_escaped_chars/grammar.js b/test/fixtures/test_grammars/anonymous_tokens_with_escaped_chars/grammar.js
index 3e7e294b..cffe374d 100644
--- a/test/fixtures/test_grammars/anonymous_tokens_with_escaped_chars/grammar.js
+++ b/test/fixtures/test_grammars/anonymous_tokens_with_escaped_chars/grammar.js
@@ -4,7 +4,7 @@
// characters. This grammar tests that this escaping works. The test is basically that the generated
// parser compiles successfully.
-module.exports = grammar({
+export default grammar({
name: "anonymous_tokens_with_escaped_chars",
rules: {
first_rule: $ => choice(
diff --git a/test/fixtures/test_grammars/associativity_left/grammar.js b/test/fixtures/test_grammars/associativity_left/grammar.js
index 6dbc4671..3b0b8e54 100644
--- a/test/fixtures/test_grammars/associativity_left/grammar.js
+++ b/test/fixtures/test_grammars/associativity_left/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'associativity_left',
rules: {
diff --git a/test/fixtures/test_grammars/associativity_missing/grammar.js b/test/fixtures/test_grammars/associativity_missing/grammar.js
index 9c1ed980..c540d132 100644
--- a/test/fixtures/test_grammars/associativity_missing/grammar.js
+++ b/test/fixtures/test_grammars/associativity_missing/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'associativity_missing',
rules: {
diff --git a/test/fixtures/test_grammars/associativity_right/grammar.js b/test/fixtures/test_grammars/associativity_right/grammar.js
index 69bfd065..a45a02fd 100644
--- a/test/fixtures/test_grammars/associativity_right/grammar.js
+++ b/test/fixtures/test_grammars/associativity_right/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'associativity_right',
rules: {
diff --git a/test/fixtures/test_grammars/conflict_in_repeat_rule/grammar.js b/test/fixtures/test_grammars/conflict_in_repeat_rule/grammar.js
index c23e8a7c..e3d4b2d8 100644
--- a/test/fixtures/test_grammars/conflict_in_repeat_rule/grammar.js
+++ b/test/fixtures/test_grammars/conflict_in_repeat_rule/grammar.js
@@ -2,7 +2,7 @@
// parser generator in order to implement repetition. There is no way of referring to these rules in
// the grammar DSL, so these conflicts must be resolved by referring to their parent rules.
-module.exports = grammar({
+export default grammar({
name: 'conflict_in_repeat_rule',
rules: {
diff --git a/test/fixtures/test_grammars/conflict_in_repeat_rule_after_external_token/grammar.js b/test/fixtures/test_grammars/conflict_in_repeat_rule_after_external_token/grammar.js
index 27364b22..85145f7f 100644
--- a/test/fixtures/test_grammars/conflict_in_repeat_rule_after_external_token/grammar.js
+++ b/test/fixtures/test_grammars/conflict_in_repeat_rule_after_external_token/grammar.js
@@ -2,7 +2,7 @@
// after an external token is consumed. This tests that the logic for determining the repeat rule's
// "parent" rule works in the presence of external tokens.
-module.exports = grammar({
+export default grammar({
name: 'conflict_in_repeat_rule_after_external_token',
externals: $ => [
@@ -29,4 +29,4 @@ module.exports = grammar({
identifier: $ => /[a-z]+/
}
-});
\ No newline at end of file
+});
diff --git a/test/fixtures/test_grammars/conflicting_precedence/grammar.js b/test/fixtures/test_grammars/conflicting_precedence/grammar.js
index 8092f8e9..98b41def 100644
--- a/test/fixtures/test_grammars/conflicting_precedence/grammar.js
+++ b/test/fixtures/test_grammars/conflicting_precedence/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'conflicting_precedence',
rules: {
diff --git a/test/fixtures/test_grammars/depends_on_column/grammar.js b/test/fixtures/test_grammars/depends_on_column/grammar.js
index 6f74810e..95646d86 100644
--- a/test/fixtures/test_grammars/depends_on_column/grammar.js
+++ b/test/fixtures/test_grammars/depends_on_column/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: "depends_on_column",
rules: {
x_is_at: ($) => seq(/[ \r\n]*/, choice($.odd_column, $.even_column), "x"),
diff --git a/test/fixtures/test_grammars/dynamic_precedence/grammar.js b/test/fixtures/test_grammars/dynamic_precedence/grammar.js
index 321f1139..e4ad3a8e 100644
--- a/test/fixtures/test_grammars/dynamic_precedence/grammar.js
+++ b/test/fixtures/test_grammars/dynamic_precedence/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'dynamic_precedence',
extras: $ => [/\s/],
diff --git a/test/fixtures/test_grammars/epsilon_external_extra_tokens/grammar.js b/test/fixtures/test_grammars/epsilon_external_extra_tokens/grammar.js
index b808de62..aefa4e0f 100644
--- a/test/fixtures/test_grammars/epsilon_external_extra_tokens/grammar.js
+++ b/test/fixtures/test_grammars/epsilon_external_extra_tokens/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'epsilon_external_extra_tokens',
extras: $ => [/\s/, $.comment],
diff --git a/test/fixtures/test_grammars/epsilon_external_tokens/grammar.js b/test/fixtures/test_grammars/epsilon_external_tokens/grammar.js
index 27deef47..14163d80 100644
--- a/test/fixtures/test_grammars/epsilon_external_tokens/grammar.js
+++ b/test/fixtures/test_grammars/epsilon_external_tokens/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'epsilon_external_tokens',
extras: $ => [/\s/],
diff --git a/test/fixtures/test_grammars/epsilon_rules/grammar.js b/test/fixtures/test_grammars/epsilon_rules/grammar.js
index 8cba729b..159ccd9f 100644
--- a/test/fixtures/test_grammars/epsilon_rules/grammar.js
+++ b/test/fixtures/test_grammars/epsilon_rules/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'epsilon_rules',
rules: {
diff --git a/test/fixtures/test_grammars/external_and_internal_anonymous_tokens/grammar.js b/test/fixtures/test_grammars/external_and_internal_anonymous_tokens/grammar.js
index e289f5ad..6e74b9d3 100644
--- a/test/fixtures/test_grammars/external_and_internal_anonymous_tokens/grammar.js
+++ b/test/fixtures/test_grammars/external_and_internal_anonymous_tokens/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'external_and_internal_anonymous_tokens',
externals: $ => [
diff --git a/test/fixtures/test_grammars/external_and_internal_tokens/grammar.js b/test/fixtures/test_grammars/external_and_internal_tokens/grammar.js
index 6b70c98a..370a5723 100644
--- a/test/fixtures/test_grammars/external_and_internal_tokens/grammar.js
+++ b/test/fixtures/test_grammars/external_and_internal_tokens/grammar.js
@@ -2,7 +2,7 @@
// validity of an *internal* token. This is done by including the names of that internal token
// (`line_break`) in the grammar's `externals` field.
-module.exports = grammar({
+export default grammar({
name: 'external_and_internal_tokens',
externals: $ => [
diff --git a/test/fixtures/test_grammars/external_extra_tokens/grammar.js b/test/fixtures/test_grammars/external_extra_tokens/grammar.js
index a5390c8a..c64ecdc1 100644
--- a/test/fixtures/test_grammars/external_extra_tokens/grammar.js
+++ b/test/fixtures/test_grammars/external_extra_tokens/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: "external_extra_tokens",
externals: $ => [
diff --git a/test/fixtures/test_grammars/external_tokens/grammar.js b/test/fixtures/test_grammars/external_tokens/grammar.js
index 457eee94..c4ff6cb7 100644
--- a/test/fixtures/test_grammars/external_tokens/grammar.js
+++ b/test/fixtures/test_grammars/external_tokens/grammar.js
@@ -2,7 +2,7 @@
// that track the nesting depth of parentheses, similar to Ruby's percent
// string literals.
-module.exports = grammar({
+export default grammar({
name: "external_tokens",
externals: $ => [
diff --git a/test/fixtures/test_grammars/external_unicode_column_alignment/grammar.js b/test/fixtures/test_grammars/external_unicode_column_alignment/grammar.js
index 3016b31d..e578920c 100644
--- a/test/fixtures/test_grammars/external_unicode_column_alignment/grammar.js
+++ b/test/fixtures/test_grammars/external_unicode_column_alignment/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: "external_unicode_column_alignment",
externals: $ => [
diff --git a/test/fixtures/test_grammars/extra_non_terminals/grammar.js b/test/fixtures/test_grammars/extra_non_terminals/grammar.js
index e66bc9ac..116cb0a6 100644
--- a/test/fixtures/test_grammars/extra_non_terminals/grammar.js
+++ b/test/fixtures/test_grammars/extra_non_terminals/grammar.js
@@ -1,6 +1,6 @@
// This grammar has an "extra" rule, `comment`, that is a non-terminal.
-module.exports = grammar({
+export default grammar({
name: "extra_non_terminals",
extras: $ => [
diff --git a/test/fixtures/test_grammars/extra_non_terminals_with_shared_rules/grammar.js b/test/fixtures/test_grammars/extra_non_terminals_with_shared_rules/grammar.js
index 28539871..993e89b4 100644
--- a/test/fixtures/test_grammars/extra_non_terminals_with_shared_rules/grammar.js
+++ b/test/fixtures/test_grammars/extra_non_terminals_with_shared_rules/grammar.js
@@ -1,7 +1,7 @@
// This grammar has a non-terminal extra rule `macro_statement` that contains
// child rules that are also used elsewhere in the grammar.
-module.exports = grammar({
+export default grammar({
name: "extra_non_terminals_with_shared_rules",
extras: $ => [/\s+/, $.macro_statement],
diff --git a/test/fixtures/test_grammars/get_col_eof/grammar.js b/test/fixtures/test_grammars/get_col_eof/grammar.js
index 3b70db2f..a2cf6ef4 100644
--- a/test/fixtures/test_grammars/get_col_eof/grammar.js
+++ b/test/fixtures/test_grammars/get_col_eof/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: "get_col_eof",
externals: $ => [
diff --git a/test/fixtures/test_grammars/get_col_should_hang_not_crash/grammar.js b/test/fixtures/test_grammars/get_col_should_hang_not_crash/grammar.js
index 83d57d2c..d45ea967 100644
--- a/test/fixtures/test_grammars/get_col_should_hang_not_crash/grammar.js
+++ b/test/fixtures/test_grammars/get_col_should_hang_not_crash/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'get_col_should_hang_not_crash',
externals: $ => [
diff --git a/test/fixtures/test_grammars/immediate_tokens/grammar.js b/test/fixtures/test_grammars/immediate_tokens/grammar.js
index 7505a811..223de3d3 100644
--- a/test/fixtures/test_grammars/immediate_tokens/grammar.js
+++ b/test/fixtures/test_grammars/immediate_tokens/grammar.js
@@ -3,7 +3,7 @@
// When there are *no* leading `extras`, an immediate token is preferred over a normal token which
// would otherwise match.
-module.exports = grammar({
+export default grammar({
name: "immediate_tokens",
extras: $ => [/\s/],
diff --git a/test/fixtures/test_grammars/indirect_recursion_in_transitions/grammar.js b/test/fixtures/test_grammars/indirect_recursion_in_transitions/grammar.js
index 65ff7b45..23ce2b24 100644
--- a/test/fixtures/test_grammars/indirect_recursion_in_transitions/grammar.js
+++ b/test/fixtures/test_grammars/indirect_recursion_in_transitions/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'indirect_recursive_in_single_symbol_transitions',
rules: {
source_file: $ => repeat($._statement),
diff --git a/test/fixtures/test_grammars/inline_rules/grammar.js b/test/fixtures/test_grammars/inline_rules/grammar.js
index 4477097d..c8f3275c 100644
--- a/test/fixtures/test_grammars/inline_rules/grammar.js
+++ b/test/fixtures/test_grammars/inline_rules/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: "inline_rules",
extras: $ => [/\s/],
diff --git a/test/fixtures/test_grammars/inlined_aliased_rules/grammar.js b/test/fixtures/test_grammars/inlined_aliased_rules/grammar.js
index 8578659b..84612330 100644
--- a/test/fixtures/test_grammars/inlined_aliased_rules/grammar.js
+++ b/test/fixtures/test_grammars/inlined_aliased_rules/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: "inlined_aliased_rules",
extras: $ => [/\s/],
diff --git a/test/fixtures/test_grammars/inverted_external_token/grammar.js b/test/fixtures/test_grammars/inverted_external_token/grammar.js
index 3530d0db..e1d78b89 100644
--- a/test/fixtures/test_grammars/inverted_external_token/grammar.js
+++ b/test/fixtures/test_grammars/inverted_external_token/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: "inverted_external_token",
externals: $ => [$.line_break],
diff --git a/test/fixtures/test_grammars/invisible_start_rule/grammar.js b/test/fixtures/test_grammars/invisible_start_rule/grammar.js
index 4afa4d66..d9fdf6f5 100644
--- a/test/fixtures/test_grammars/invisible_start_rule/grammar.js
+++ b/test/fixtures/test_grammars/invisible_start_rule/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: "invisible_start_rule",
rules: {
_value: $ => choice($.a, $.b),
diff --git a/test/fixtures/test_grammars/lexical_conflicts_due_to_state_merging/grammar.js b/test/fixtures/test_grammars/lexical_conflicts_due_to_state_merging/grammar.js
index 4868dc81..2dc3639b 100644
--- a/test/fixtures/test_grammars/lexical_conflicts_due_to_state_merging/grammar.js
+++ b/test/fixtures/test_grammars/lexical_conflicts_due_to_state_merging/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'lexical_conflicts_due_to_state_merging',
rules: {
diff --git a/test/fixtures/test_grammars/named_precedences/grammar.js b/test/fixtures/test_grammars/named_precedences/grammar.js
index 2132385b..52c767c5 100644
--- a/test/fixtures/test_grammars/named_precedences/grammar.js
+++ b/test/fixtures/test_grammars/named_precedences/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'named_precedences',
conflicts: $ => [
diff --git a/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/grammar.js b/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/grammar.js
index 3f30de56..6bc56822 100644
--- a/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/grammar.js
+++ b/test/fixtures/test_grammars/named_rule_aliased_as_anonymous/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'named_rule_aliased_as_anonymous',
rules: {
diff --git a/test/fixtures/test_grammars/nested_inlined_rules/grammar.js b/test/fixtures/test_grammars/nested_inlined_rules/grammar.js
index 7aaf601b..3ca4d264 100644
--- a/test/fixtures/test_grammars/nested_inlined_rules/grammar.js
+++ b/test/fixtures/test_grammars/nested_inlined_rules/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'nested_inlined_rules',
inline: $ => [
diff --git a/test/fixtures/test_grammars/next_sibling_from_zwt/grammar.js b/test/fixtures/test_grammars/next_sibling_from_zwt/grammar.js
index 39c3c0ef..f9a31c44 100644
--- a/test/fixtures/test_grammars/next_sibling_from_zwt/grammar.js
+++ b/test/fixtures/test_grammars/next_sibling_from_zwt/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: "next_sibling_from_zwt",
extras: $ => [
/\s|\\\r?\n/,
diff --git a/test/fixtures/test_grammars/partially_resolved_conflict/grammar.js b/test/fixtures/test_grammars/partially_resolved_conflict/grammar.js
index cd0d1d65..23b11167 100644
--- a/test/fixtures/test_grammars/partially_resolved_conflict/grammar.js
+++ b/test/fixtures/test_grammars/partially_resolved_conflict/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'partially_resolved_conflict',
rules: {
diff --git a/test/fixtures/test_grammars/precedence_on_single_child_missing/grammar.js b/test/fixtures/test_grammars/precedence_on_single_child_missing/grammar.js
index fbdb450f..0c9c1b45 100644
--- a/test/fixtures/test_grammars/precedence_on_single_child_missing/grammar.js
+++ b/test/fixtures/test_grammars/precedence_on_single_child_missing/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'precedence_on_single_child_missing',
rules: {
diff --git a/test/fixtures/test_grammars/precedence_on_single_child_negative/grammar.js b/test/fixtures/test_grammars/precedence_on_single_child_negative/grammar.js
index 798075db..6d9ea114 100644
--- a/test/fixtures/test_grammars/precedence_on_single_child_negative/grammar.js
+++ b/test/fixtures/test_grammars/precedence_on_single_child_negative/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'precedence_on_single_child_negative',
rules: {
diff --git a/test/fixtures/test_grammars/precedence_on_single_child_positive/grammar.js b/test/fixtures/test_grammars/precedence_on_single_child_positive/grammar.js
index d2e57c30..0492f0ac 100644
--- a/test/fixtures/test_grammars/precedence_on_single_child_positive/grammar.js
+++ b/test/fixtures/test_grammars/precedence_on_single_child_positive/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'precedence_on_single_child_positive',
rules: {
diff --git a/test/fixtures/test_grammars/precedence_on_subsequence/grammar.js b/test/fixtures/test_grammars/precedence_on_subsequence/grammar.js
index 3a5bdefb..f6caad5e 100644
--- a/test/fixtures/test_grammars/precedence_on_subsequence/grammar.js
+++ b/test/fixtures/test_grammars/precedence_on_subsequence/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'precedence_on_subsequence',
rules: {
diff --git a/test/fixtures/test_grammars/precedence_on_token/grammar.js b/test/fixtures/test_grammars/precedence_on_token/grammar.js
index e56f2d81..67a0b9bf 100644
--- a/test/fixtures/test_grammars/precedence_on_token/grammar.js
+++ b/test/fixtures/test_grammars/precedence_on_token/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'precedence_on_token',
extras: $ => [
diff --git a/test/fixtures/test_grammars/readme_grammar/grammar.js b/test/fixtures/test_grammars/readme_grammar/grammar.js
index a24878df..0547df4c 100644
--- a/test/fixtures/test_grammars/readme_grammar/grammar.js
+++ b/test/fixtures/test_grammars/readme_grammar/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'readme_grammar',
// Things that can appear anywhere in the language, like comments
diff --git a/test/fixtures/test_grammars/reserved_words/grammar.js b/test/fixtures/test_grammars/reserved_words/grammar.js
index 67d0253e..74c88eea 100644
--- a/test/fixtures/test_grammars/reserved_words/grammar.js
+++ b/test/fixtures/test_grammars/reserved_words/grammar.js
@@ -1,7 +1,7 @@
const RESERVED_NAMES = ["if", "while", "var"];
const RESERVED_PROPERTY_NAMES = ["var"];
-module.exports = grammar({
+export default grammar({
name: "reserved_words",
reserved: {
diff --git a/test/fixtures/test_grammars/start_rule_is_blank/grammar.js b/test/fixtures/test_grammars/start_rule_is_blank/grammar.js
index b38e0de0..6fa28ae5 100644
--- a/test/fixtures/test_grammars/start_rule_is_blank/grammar.js
+++ b/test/fixtures/test_grammars/start_rule_is_blank/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'start_rule_is_blank',
rules: {
diff --git a/test/fixtures/test_grammars/start_rule_is_token/grammar.js b/test/fixtures/test_grammars/start_rule_is_token/grammar.js
index f00433ea..05742687 100644
--- a/test/fixtures/test_grammars/start_rule_is_token/grammar.js
+++ b/test/fixtures/test_grammars/start_rule_is_token/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'start_rule_is_token',
rules: {
diff --git a/test/fixtures/test_grammars/unicode_classes/grammar.js b/test/fixtures/test_grammars/unicode_classes/grammar.js
index 25dcf13d..2aafdea4 100644
--- a/test/fixtures/test_grammars/unicode_classes/grammar.js
+++ b/test/fixtures/test_grammars/unicode_classes/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'unicode_classes',
rules: {
diff --git a/test/fixtures/test_grammars/unused_rules/grammar.js b/test/fixtures/test_grammars/unused_rules/grammar.js
index 462243c8..78020419 100644
--- a/test/fixtures/test_grammars/unused_rules/grammar.js
+++ b/test/fixtures/test_grammars/unused_rules/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'unused_rules',
rules: {
diff --git a/test/fixtures/test_grammars/uses_current_column/grammar.js b/test/fixtures/test_grammars/uses_current_column/grammar.js
index 795ad597..1e93f06b 100644
--- a/test/fixtures/test_grammars/uses_current_column/grammar.js
+++ b/test/fixtures/test_grammars/uses_current_column/grammar.js
@@ -1,4 +1,4 @@
-module.exports = grammar({
+export default grammar({
name: 'uses_current_column',
externals: $ => [