From c9a82d08ac09cdb1a3f7c4a7c799764a917cfb08 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Thu, 13 Aug 2026 11:46:10 +0200 Subject: [PATCH 1/2] feat(`json-imports-to-default-imports`): introduce --- .changeset/stale-lands-rhyme.md | 5 + .../json-imports-to-default-imports/README.md | 37 ++++++ .../codemod.yaml | 20 +++ .../package.json | 13 ++ .../src/workflow.ts | 124 ++++++++++++++++++ .../tests/test-1/expected.ts | 2 + .../tests/test-1/input.ts | 2 + .../tests/test-2/expected.ts | 2 + .../tests/test-2/input.ts | 2 + .../tests/test-3/expected.ts | 2 + .../tests/test-3/input.ts | 2 + .../tests/test-4/expected.ts | 2 + .../tests/test-4/input.ts | 2 + .../workflow.yaml | 25 ++++ 14 files changed, 240 insertions(+) create mode 100644 .changeset/stale-lands-rhyme.md create mode 100644 codemods/json-imports-to-default-imports/README.md create mode 100644 codemods/json-imports-to-default-imports/codemod.yaml create mode 100644 codemods/json-imports-to-default-imports/package.json create mode 100644 codemods/json-imports-to-default-imports/src/workflow.ts create mode 100644 codemods/json-imports-to-default-imports/tests/test-1/expected.ts create mode 100644 codemods/json-imports-to-default-imports/tests/test-1/input.ts create mode 100644 codemods/json-imports-to-default-imports/tests/test-2/expected.ts create mode 100644 codemods/json-imports-to-default-imports/tests/test-2/input.ts create mode 100644 codemods/json-imports-to-default-imports/tests/test-3/expected.ts create mode 100644 codemods/json-imports-to-default-imports/tests/test-3/input.ts create mode 100644 codemods/json-imports-to-default-imports/tests/test-4/expected.ts create mode 100644 codemods/json-imports-to-default-imports/tests/test-4/input.ts create mode 100644 codemods/json-imports-to-default-imports/workflow.yaml diff --git a/.changeset/stale-lands-rhyme.md b/.changeset/stale-lands-rhyme.md new file mode 100644 index 0000000..f0d2567 --- /dev/null +++ b/.changeset/stale-lands-rhyme.md @@ -0,0 +1,5 @@ +--- +"@webpack/json-imports-to-default-importss": major +--- + +Introduce the codemod, by copy pasting it here from `codemod/webpack-codemods` diff --git a/codemods/json-imports-to-default-imports/README.md b/codemods/json-imports-to-default-imports/README.md new file mode 100644 index 0000000..ea06dd3 --- /dev/null +++ b/codemods/json-imports-to-default-imports/README.md @@ -0,0 +1,37 @@ +# json-imports-to-default-imports + +This codemod migrates imports from JSON modules that use named exports to use default exports instead. + +This codemod transforms named imports from JSON files into default imports, adhering to the new ECMAScript specification and Webpack v5 compatibility. Named imports from JSON modules are no longer supported. + +[Official Documentation](https://webpack.js.org/migrate/5/#using-named-exports-from-json-modules) + +## Examples + +```diff +- import { version } from "./package.json"; +- console.log(version); ++ import pkg from "./package.json"; ++ console.log(pkg.version); +``` + +```diff +- import { version, name, description } from "./package.json"; +- console.log(version, name, description); ++ import pkg from "./package.json"; ++ console.log(pkg.version, pkg.name, pkg.description); +``` + +```diff +- import { data } from './config.json'; +- console.log(data.nested.key, data.anotherKey); ++ import config from './config.json'; ++ console.log(config.data.nested.key, config.data.anotherKey); +``` + +```diff +- import { key1, key2 } from './config.json'; +- console.log(key1, key2); ++ import config from './config.json'; ++ console.log(config.key1, config.key2); +``` diff --git a/codemods/json-imports-to-default-imports/codemod.yaml b/codemods/json-imports-to-default-imports/codemod.yaml new file mode 100644 index 0000000..312dbf6 --- /dev/null +++ b/codemods/json-imports-to-default-imports/codemod.yaml @@ -0,0 +1,20 @@ +schema_version: "1.0" +name: "@webpack/json-imports-to-default-imports" +version: 1.0.0 +description: Transform JSON named imports to default imports +author: akash-kumar-dev +license: MIT +workflow: workflow.yaml +category: migration + +targets: + languages: + - javascript + - typescript + +keywords: + - webpack + +registry: + access: public + visibility: public \ No newline at end of file diff --git a/codemods/json-imports-to-default-imports/package.json b/codemods/json-imports-to-default-imports/package.json new file mode 100644 index 0000000..1e83c8e --- /dev/null +++ b/codemods/json-imports-to-default-imports/package.json @@ -0,0 +1,13 @@ +{ + "name": "@webpack/json-imports-to-default-importss", + "private": true, + "version": "1.0.0", + "description": "Transform JSON named imports to default imports", + "type": "module", + "scripts": { + "test": "npx codemod jssg test -l typescript ./src/workflow.ts" + }, + "devDependencies": { + "@codemod.com/jssg-types": "^1.6.3" + } +} diff --git a/codemods/json-imports-to-default-imports/src/workflow.ts b/codemods/json-imports-to-default-imports/src/workflow.ts new file mode 100644 index 0000000..2c63397 --- /dev/null +++ b/codemods/json-imports-to-default-imports/src/workflow.ts @@ -0,0 +1,124 @@ +import type { Codemod, Edit } from "codemod:ast-grep"; +import type Js from "@codemod.com/jssg-types/langs/javascript"; + + +const transform: Codemod = async (root) => { + const rootNode = root.root(); + const edits: Edit[] = []; + + // Find all import statements from JSON files + const allImportStatements = rootNode.findAll({ + rule: { + kind: "import_statement", + }, + }); + + // Track transformations to apply identifier replacements + const transformations: Array<{ + importedNames: string[]; + defaultImportName: string; + importPath: string; + }> = []; + + for (const importStatement of allImportStatements) { + // Check if this import has a string source ending with .json + const sourceStrings = importStatement.findAll({ + rule: { + kind: "string", + }, + }); + + if (sourceStrings.length === 0) continue; + + const sourceString = sourceStrings[0]; + const sourceText = sourceString.text(); + const importPath = sourceText.slice(1, -1); + + if (!importPath.endsWith(".json")) continue; + + // Check if this import has named imports + const namedImports = importStatement.findAll({ + rule: { + kind: "named_imports", + }, + }); + + if (namedImports.length === 0) continue; + + // Extract import specifiers + const importSpecifiers = importStatement.findAll({ + rule: { + kind: "import_specifier", + }, + }); + + if (importSpecifiers.length === 0) continue; + + // Extract the names of imported identifiers + const importedNames: string[] = []; + for (const specifier of importSpecifiers) { + const identifiers = specifier.findAll({ + rule: { + kind: "identifier", + }, + }); + + if (identifiers.length > 0) { + const localName = identifiers[identifiers.length - 1].text(); + importedNames.push(localName); + } + } + + if (importedNames.length === 0) continue; + + // Generate default import name + const importBaseName = importPath.split("/").pop()?.replace(".json", "") || "config"; + const defaultImportName = importBaseName === "package" ? "pkg" : importBaseName; + + edits.push(importStatement.replace(`import ${defaultImportName} from ${sourceText};`)); + + // Track this transformation for identifier replacement + transformations.push({ + importedNames, + defaultImportName, + importPath, + }); + } + + // Replace all usages of the imported identifiers with property access + for (const { importedNames, defaultImportName } of transformations) { + for (const importedName of importedNames) { + // Find all identifiers with this exact name + const identifiers = rootNode.findAll({ + rule: { + kind: "identifier", + regex: `^${escapeRegex(importedName)}$`, + }, + }); + + for (const identifier of identifiers) { + // Skip if this identifier is part of an import statement + const isInImportStatement = identifier.inside({ + rule: { + kind: "import_statement", + }, + }); + + if (isInImportStatement) continue; + + // Replace with property access + edits.push(identifier.replace(`${defaultImportName}.${importedName}`)); + } + } + } + + if (!edits.length) return null; + + return rootNode.commitEdits(edits); +} + +function escapeRegex(str: string): string { + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +export default transform; diff --git a/codemods/json-imports-to-default-imports/tests/test-1/expected.ts b/codemods/json-imports-to-default-imports/tests/test-1/expected.ts new file mode 100644 index 0000000..33a0251 --- /dev/null +++ b/codemods/json-imports-to-default-imports/tests/test-1/expected.ts @@ -0,0 +1,2 @@ +import pkg from "./package.json"; +console.log(pkg.version); diff --git a/codemods/json-imports-to-default-imports/tests/test-1/input.ts b/codemods/json-imports-to-default-imports/tests/test-1/input.ts new file mode 100644 index 0000000..a6fa878 --- /dev/null +++ b/codemods/json-imports-to-default-imports/tests/test-1/input.ts @@ -0,0 +1,2 @@ +import { version } from "./package.json"; +console.log(version); diff --git a/codemods/json-imports-to-default-imports/tests/test-2/expected.ts b/codemods/json-imports-to-default-imports/tests/test-2/expected.ts new file mode 100644 index 0000000..7a019c5 --- /dev/null +++ b/codemods/json-imports-to-default-imports/tests/test-2/expected.ts @@ -0,0 +1,2 @@ +import pkg from "./package.json"; +console.log(pkg.version, pkg.name, pkg.description); diff --git a/codemods/json-imports-to-default-imports/tests/test-2/input.ts b/codemods/json-imports-to-default-imports/tests/test-2/input.ts new file mode 100644 index 0000000..6ae3379 --- /dev/null +++ b/codemods/json-imports-to-default-imports/tests/test-2/input.ts @@ -0,0 +1,2 @@ +import { version, name, description } from "./package.json"; +console.log(version, name, description); diff --git a/codemods/json-imports-to-default-imports/tests/test-3/expected.ts b/codemods/json-imports-to-default-imports/tests/test-3/expected.ts new file mode 100644 index 0000000..c81aac6 --- /dev/null +++ b/codemods/json-imports-to-default-imports/tests/test-3/expected.ts @@ -0,0 +1,2 @@ +import config from "./config.json"; +console.log(config.data.nested.key, config.data.anotherKey); diff --git a/codemods/json-imports-to-default-imports/tests/test-3/input.ts b/codemods/json-imports-to-default-imports/tests/test-3/input.ts new file mode 100644 index 0000000..341d341 --- /dev/null +++ b/codemods/json-imports-to-default-imports/tests/test-3/input.ts @@ -0,0 +1,2 @@ +import { data } from "./config.json"; +console.log(data.nested.key, data.anotherKey); diff --git a/codemods/json-imports-to-default-imports/tests/test-4/expected.ts b/codemods/json-imports-to-default-imports/tests/test-4/expected.ts new file mode 100644 index 0000000..9072fbc --- /dev/null +++ b/codemods/json-imports-to-default-imports/tests/test-4/expected.ts @@ -0,0 +1,2 @@ +import config from "./config.json"; +console.log(config.key1, config.key2); diff --git a/codemods/json-imports-to-default-imports/tests/test-4/input.ts b/codemods/json-imports-to-default-imports/tests/test-4/input.ts new file mode 100644 index 0000000..176c34c --- /dev/null +++ b/codemods/json-imports-to-default-imports/tests/test-4/input.ts @@ -0,0 +1,2 @@ +import { key1, key2 } from "./config.json"; +console.log(key1, key2); diff --git a/codemods/json-imports-to-default-imports/workflow.yaml b/codemods/json-imports-to-default-imports/workflow.yaml new file mode 100644 index 0000000..2e81ef7 --- /dev/null +++ b/codemods/json-imports-to-default-imports/workflow.yaml @@ -0,0 +1,25 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod-com/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: Transform JSON named imports to default imports + js-ast-grep: + js_file: src/workflow.ts + base_path: . + include: + - "**/*.cjs" + - "**/*.js" + - "**/*.jsx" + - "**/*.mjs" + - "**/*.cts" + - "**/*.mts" + - "**/*.ts" + - "**/*.tsx" + exclude: + - "**/node_modules/**" + language: typescript \ No newline at end of file From 1c427a191a76f2420290bf8903fee1b0d5fb4dec Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:02:29 +0200 Subject: [PATCH 2/2] Update package-lock.json --- package-lock.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 3f0ce0f..6c50a26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ }, "codemods/css-plugins-to-native-css": { "name": "@webpack/css-plugins-to-native-css", - "version": "0.0.0", + "version": "1.0.0", "license": "MIT", "dependencies": { "@webpack/codemod-utils": "*" @@ -32,6 +32,13 @@ "@codemod.com/jssg-types": "^1.6.2" } }, + "codemods/json-imports-to-default-imports": { + "name": "@webpack/json-imports-to-default-importss", + "version": "1.0.0", + "devDependencies": { + "@codemod.com/jssg-types": "^1.6.3" + } + }, "node_modules/@babel/runtime": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", @@ -1029,6 +1036,10 @@ "resolved": "codemods/css-plugins-to-native-css", "link": true }, + "node_modules/@webpack/json-imports-to-default-importss": { + "resolved": "codemods/json-imports-to-default-imports", + "link": true + }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",