Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/stale-lands-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@webpack/json-imports-to-default-importss": major
---

Introduce the codemod, by copy pasting it here from `codemod/webpack-codemods`
37 changes: 37 additions & 0 deletions codemods/json-imports-to-default-imports/README.md
Original file line number Diff line number Diff line change
@@ -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);
```
20 changes: 20 additions & 0 deletions codemods/json-imports-to-default-imports/codemod.yaml
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions codemods/json-imports-to-default-imports/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
124 changes: 124 additions & 0 deletions codemods/json-imports-to-default-imports/src/workflow.ts
Original file line number Diff line number Diff line change
@@ -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<Js> = 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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import pkg from "./package.json";
console.log(pkg.version);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { version } from "./package.json";
console.log(version);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import pkg from "./package.json";
console.log(pkg.version, pkg.name, pkg.description);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { version, name, description } from "./package.json";
console.log(version, name, description);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import config from "./config.json";
console.log(config.data.nested.key, config.data.anotherKey);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { data } from "./config.json";
console.log(data.nested.key, data.anotherKey);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import config from "./config.json";
console.log(config.key1, config.key2);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { key1, key2 } from "./config.json";
console.log(key1, key2);
25 changes: 25 additions & 0 deletions codemods/json-imports-to-default-imports/workflow.yaml
Original file line number Diff line number Diff line change
@@ -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
13 changes: 12 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.