Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental cra-to-next transform in codemod cli #24969

Merged
merged 27 commits into from
Jun 9, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9699ddd
Add initial cra-to-next transform in codemod cli
ijjk May 9, 2021
f011541
add install, ignores, next.config.js
ijjk May 9, 2021
db2a91f
ensure file-loader is used for SSR as well
ijjk May 9, 2021
03e8efa
enable webpack5 by default
ijjk May 10, 2021
6cbb24c
Migrate tags from index.html
ijjk May 10, 2021
40b0eff
Add note about webpack5 option
ijjk May 10, 2021
7b99f2c
handle cases from testing
ijjk May 10, 2021
cbb8567
only create babelrc when svgr is used
ijjk May 10, 2021
c3dffcc
fix file-loader compat with webpack 5
ijjk May 10, 2021
6df3ba5
Merge remote-tracking branch 'upstream/canary' into feat/cra-to-next-cli
ijjk May 10, 2021
6734c30
fix type
ijjk May 10, 2021
7e73ec5
update craCompat require from testing
ijjk May 10, 2021
c17e219
Merge branch 'canary' into feat/cra-to-next-cli
ijjk May 17, 2021
ff2b505
Merge remote-tracking branch 'upstream/canary' into feat/cra-to-next-cli
ijjk May 23, 2021
6547567
Add nested render error
ijjk May 24, 2021
1f95e37
Merge remote-tracking branch 'upstream/canary' into feat/cra-to-next-cli
ijjk May 24, 2021
bd02321
revert test import change
ijjk May 24, 2021
4feec9b
Dont allow import {ReactComponent} from "./svg"
ijjk Jun 3, 2021
0df75b0
Merge remote-tracking branch 'upstream/canary' into feat/cra-to-next-cli
ijjk Jun 3, 2021
a39265a
Allow global CSS imports in node_modules for compat
ijjk Jun 3, 2021
70565a6
Merge remote-tracking branch 'upstream/canary' into feat/cra-to-next-cli
ijjk Jun 7, 2021
967f33d
Add feedback link
ijjk Jun 7, 2021
6deba72
Merge remote-tracking branch 'upstream/canary' into feat/cra-to-next-cli
ijjk Jun 8, 2021
761309e
Move CRA compat inside webpack-config
ijjk Jun 8, 2021
8254796
Merge remote-tracking branch 'upstream/canary' into feat/cra-to-next-cli
ijjk Jun 8, 2021
226cc02
Update packages/next-codemod/bin/cli.ts
timneutkens Jun 9, 2021
75069e9
Merge branch 'canary' into feat/cra-to-next-cli
kodiakhq[bot] Jun 9, 2021
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
71 changes: 37 additions & 34 deletions packages/next-codemod/bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
// Based on https://github.com/reactjs/react-codemod/blob/dd8671c9a470a2c342b221ec903c574cf31e9f57/bin/cli.js
// @next/codemod optional-name-of-transform optional/path/to/src [...options]

const globby = require('globby')
const inquirer = require('inquirer')
const meow = require('meow')
const path = require('path')
const execa = require('execa')
const chalk = require('chalk')
const isGitClean = require('is-git-clean')

const transformerDirectory = path.join(__dirname, '../', 'transforms')
const jscodeshiftExecutable = require.resolve('.bin/jscodeshift')

function checkGitStatus(force) {
import globby from 'globby'
import inquirer from 'inquirer'
import meow from 'meow'
import path from 'path'
import execa from 'execa'
import chalk from 'chalk'
import isGitClean from 'is-git-clean'

export const jscodeshiftExecutable = require.resolve('.bin/jscodeshift')
export const transformerDirectory = path.join(__dirname, '../', 'transforms')

export function checkGitStatus(force) {
let clean = false
let errorMessage = 'Unable to determine if git directory is clean'
try {
Expand Down Expand Up @@ -49,19 +49,27 @@ function checkGitStatus(force) {
}
}

function runTransform({ files, flags, transformer }) {
export function runTransform({ files, flags, transformer }) {
const transformerPath = path.join(transformerDirectory, `${transformer}.js`)

if (transformer === 'cra-to-next') {
// cra-to-next transform doesn't use jscodeshift directly
return require(transformerPath).default(files, flags)
}

let args = []

const { dry, print } = flags
const { dry, print, runInBand } = flags

if (dry) {
args.push('--dry')
}
if (print) {
args.push('--print')
}
if (runInBand) {
args.push('--run-in-band')
}

args.push('--verbose=2')

Expand All @@ -83,11 +91,11 @@ function runTransform({ files, flags, transformer }) {

const result = execa.sync(jscodeshiftExecutable, args, {
stdio: 'inherit',
stripEof: false,
stripFinalNewline: false,
})

if (result.error) {
throw result.error
if (result.failed) {
throw new Error(`jscodeshift exited with code ${result.exitCode}`)
}
}

Expand All @@ -112,6 +120,11 @@ const TRANSFORMER_INQUIRER_CHOICES = [
'url-to-withrouter: Transforms the deprecated automatically injected url property on top level pages to using withRouter',
value: 'url-to-withrouter',
},
{
name:
'cra-to-next: automatically migrates a create-react-app project to leverage Next.js instead',
timneutkens marked this conversation as resolved.
Show resolved Hide resolved
value: 'cra-to-next',
},
]

function expandFilePathsIfNeeded(filesBeforeExpansion) {
Expand All @@ -123,11 +136,10 @@ function expandFilePathsIfNeeded(filesBeforeExpansion) {
: filesBeforeExpansion
}

function run() {
const cli = meow(
{
description: 'Codemods for updating Next.js apps.',
help: `
export function run() {
const cli = meow({
description: 'Codemods for updating Next.js apps.',
help: `
Usage
$ npx @next/codemod <transform> <path> <...options>
transform One of the choices from https://github.com/vercel/next.js/tree/canary/packages/next-codemod
Expand All @@ -138,15 +150,14 @@ function run() {
--print Print transformed files to your terminal
--jscodeshift (Advanced) Pass options directly to jscodeshift
`,
},
{
flags: {
boolean: ['force', 'dry', 'print', 'help'],
string: ['_'],
alias: {
h: 'help',
},
}
)
},
} as meow.Options<meow.AnyFlags>)

if (!cli.flags.dry) {
checkGitStatus(cli.flags.force)
Expand Down Expand Up @@ -203,11 +214,3 @@ function run() {
})
})
}

module.exports = {
run: run,
runTransform: runTransform,
checkGitStatus: checkGitStatus,
jscodeshiftExecutable: jscodeshiftExecutable,
transformerDirectory: transformerDirectory,
}
34 changes: 34 additions & 0 deletions packages/next-codemod/lib/cra-to-next/gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
62 changes: 62 additions & 0 deletions packages/next-codemod/lib/cra-to-next/global-css-transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import nodePath from 'path'
import { API, FileInfo, Options } from 'jscodeshift'

export const globalCssContext = {
cssImports: new Set<string>(),
hasReactSvgImport: false,
}
const globalStylesRegex = /(?<!\.module)\.(css|scss|sass)$/i

export default function transformer(
file: FileInfo,
api: API,
options: Options
) {
const j = api.jscodeshift
const root = j(file.source)
let hasModifications = false

root
.find(j.ImportDeclaration)
.filter((path) => {
const {
node: {
source: { value },
},
} = path

if (typeof value === 'string') {
if (globalStylesRegex.test(value)) {
const resolvedPath = nodePath.resolve(
nodePath.dirname(file.path),
value
)
globalCssContext.cssImports.add(resolvedPath)

const { start, end } = path.node as any

if (!path.parentPath.node.comments) {
path.parentPath.node.comments = []
}

path.parentPath.node.comments = [
j.commentLine(' ' + file.source.substring(start, end)),
]
hasModifications = true
return true
} else if (value.endsWith('.svg')) {
const isComponentImport = path.node.specifiers.some((specifier) => {
return (specifier as any).imported?.name === 'ReactComponent'
})

if (isComponentImport) {
globalCssContext.hasReactSvgImport = true
}
}
}
return false
})
.remove()

return hasModifications ? root.toSource(options) : null
}
92 changes: 92 additions & 0 deletions packages/next-codemod/lib/cra-to-next/index-to-component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { API, FileInfo, JSXElement, Options } from 'jscodeshift'

export const indexContext = {
multipleRenderRoots: false,
}

export default function transformer(
file: FileInfo,
api: API,
options: Options
) {
const j = api.jscodeshift
const root = j(file.source)
let hasModifications = false
let foundReactRender = 0
let hasRenderImport = false
let defaultReactDomImport: string | undefined

root.find(j.ImportDeclaration).forEach((path) => {
if (path.node.source.value === 'react-dom') {
return path.node.specifiers.forEach((specifier) => {
if (specifier.local.name === 'render') {
hasRenderImport = true
}
if (specifier.type === 'ImportDefaultSpecifier') {
defaultReactDomImport = specifier.local.name
}
})
}
return false
})

root
.find(j.CallExpression)
.filter((path) => {
const { node } = path
let found = false

if (
defaultReactDomImport &&
node.callee.type === 'MemberExpression' &&
(node.callee.object as any).name === defaultReactDomImport &&
(node.callee.property as any).name === 'render'
) {
found = true
}

if (hasRenderImport && (node.callee as any).name === 'render') {
found = true
}

if (found) {
foundReactRender++
hasModifications = true

const newNode = j.exportDefaultDeclaration(
j.functionDeclaration(
j.identifier('NextIndexWrapper'),
[],
j.blockStatement([
j.returnStatement(
// TODO: remove React.StrictMode wrapper and use
// next.config.js option instead?
path.node.arguments.find(
(a) => a.type === 'JSXElement'
) as JSXElement
),
])
)
)

path.parentPath.insertBefore(newNode)
return true
}
return false
})
.remove()

indexContext.multipleRenderRoots = foundReactRender > 1
hasModifications = hasModifications && !indexContext.multipleRenderRoots

// TODO: move function passed to reportWebVitals if present to
// _app reportWebVitals and massage values to expected shape

// root.find(j.CallExpression, {
// callee: {
// name: 'reportWebVitals'
// }
// }).remove()

return hasModifications ? root.toSource(options) : null
}
Loading