Upgrade to NextJS 14 (#2142)

This commit is contained in:
Thomas Brandého 2023-11-13 18:52:51 +01:00 committed by GitHub
parent 5e6520cb5d
commit 39cc1b2f12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 89 additions and 126 deletions

View File

@ -3,4 +3,5 @@ module.exports = {
compress: true, compress: true,
reactStrictMode: true, reactStrictMode: true,
trailingSlash: true, trailingSlash: true,
output: "",
}; };

View File

@ -95,8 +95,8 @@ class PackageJson(SimpleNamespace):
"""The commands to define in package.json.""" """The commands to define in package.json."""
DEV = "next dev" DEV = "next dev"
EXPORT = "next build && next export -o _static" EXPORT = "next build"
EXPORT_SITEMAP = "next build && next-sitemap && next export -o _static" EXPORT_SITEMAP = "next build && next-sitemap"
PROD = "next start" PROD = "next start"
PATH = os.path.join(Dirs.WEB, "package.json") PATH = os.path.join(Dirs.WEB, "package.json")
@ -106,7 +106,7 @@ class PackageJson(SimpleNamespace):
"focus-visible": "5.2.0", "focus-visible": "5.2.0",
"framer-motion": "10.16.4", "framer-motion": "10.16.4",
"json5": "2.2.3", "json5": "2.2.3",
"next": "13.5.4", "next": "14.0.1",
"next-sitemap": "4.1.8", "next-sitemap": "4.1.8",
"next-themes": "0.2.0", "next-themes": "0.2.0",
"react": "18.2.0", "react": "18.2.0",

View File

@ -184,6 +184,7 @@ def _run(
console.rule("[bold]Starting Reflex App") console.rule("[bold]Starting Reflex App")
if frontend: if frontend:
prerequisites.update_next_config()
# Get the app module. # Get the app module.
prerequisites.get_app() prerequisites.get_app()
@ -337,6 +338,8 @@ def export(
console.rule("[bold]Compiling production app and preparing for export.") console.rule("[bold]Compiling production app and preparing for export.")
if frontend: if frontend:
# Update some parameters for export
prerequisites.update_next_config(export=True)
# Ensure module can be imported and app.compile() is called. # Ensure module can be imported and app.compile() is called.
prerequisites.get_app() prerequisites.get_app()
# Set up .web directory and install frontend dependencies. # Set up .web directory and install frontend dependencies.

View File

@ -677,7 +677,7 @@ class AppHarnessProd(AppHarness):
frontend_server: Optional[Subdir404TCPServer] = None frontend_server: Optional[Subdir404TCPServer] = None
def _run_frontend(self): def _run_frontend(self):
web_root = self.app_path / reflex.constants.Dirs.WEB / "_static" web_root = self.app_path / reflex.constants.Dirs.WEB_STATIC
error_page_map = { error_page_map = {
404: web_root / "404.html", 404: web_root / "404.html",
} }

View File

@ -35,21 +35,25 @@ def set_os_env(**kwargs):
os.environ[key.upper()] = value os.environ[key.upper()] = value
def generate_sitemap_config(deploy_url: str): def generate_sitemap_config(deploy_url: str, export=False):
"""Generate the sitemap config file. """Generate the sitemap config file.
Args: Args:
deploy_url: The URL of the deployed app. deploy_url: The URL of the deployed app.
export: If the sitemap are generated for an export.
""" """
# Import here to avoid circular imports. # Import here to avoid circular imports.
from reflex.compiler import templates from reflex.compiler import templates
config = json.dumps( config = {
{ "siteUrl": deploy_url,
"siteUrl": deploy_url, "generateRobotsTxt": True,
"generateRobotsTxt": True, }
}
) if export:
config["outDir"] = constants.Dirs.STATIC
config = json.dumps(config)
with open(constants.Next.SITEMAP_CONFIG_FILE, "w") as f: with open(constants.Next.SITEMAP_CONFIG_FILE, "w") as f:
f.write(templates.SITEMAP_CONFIG(config=config)) f.write(templates.SITEMAP_CONFIG(config=config))
@ -115,7 +119,7 @@ def _zip(
with progress, zipfile.ZipFile(target, "w", zipfile.ZIP_DEFLATED) as zipf: with progress, zipfile.ZipFile(target, "w", zipfile.ZIP_DEFLATED) as zipf:
for file in files_to_zip: for file in files_to_zip:
console.debug(f"{target}: {file}") console.debug(f"{target}: {file}", progress=progress)
progress.advance(task) progress.advance(task)
zipf.write(file, os.path.relpath(file, root_dir)) zipf.write(file, os.path.relpath(file, root_dir))
@ -145,22 +149,23 @@ def export(
command = "export" command = "export"
if frontend: if frontend:
# Generate a sitemap if a deploy URL is provided.
if deploy_url is not None:
generate_sitemap_config(deploy_url)
command = "export-sitemap"
checkpoints = [ checkpoints = [
"Linting and checking ", "Linting and checking ",
"Compiled successfully", "Creating an optimized production build",
"Route (pages)", "Route (pages)",
"prerendered as static HTML",
"Collecting page data", "Collecting page data",
"automatically rendered as static HTML",
'Copying "static build" directory',
'Copying "public" directory',
"Finalizing page optimization", "Finalizing page optimization",
"Export successful", "Collecting build traces",
] ]
# Generate a sitemap if a deploy URL is provided.
if deploy_url is not None:
generate_sitemap_config(deploy_url, export=zip)
command = "export-sitemap"
checkpoints.extend(["Loading next-sitemap", "Generation completed"])
# Start the subprocess with the progress bar. # Start the subprocess with the progress bar.
process = processes.new_process( process = processes.new_process(
[prerequisites.get_package_manager(), "run", command], [prerequisites.get_package_manager(), "run", command],
@ -181,7 +186,7 @@ def export(
target=os.path.join( target=os.path.join(
zip_dest_dir, constants.ComponentName.FRONTEND.zip() zip_dest_dir, constants.ComponentName.FRONTEND.zip()
), ),
root_dir=".web/_static", root_dir=constants.Dirs.WEB_STATIC,
files_to_exclude=files_to_exclude, files_to_exclude=files_to_exclude,
exclude_venv_dirs=False, exclude_venv_dirs=False,
) )

View File

@ -45,7 +45,11 @@ def debug(msg: str, **kwargs):
kwargs: Keyword arguments to pass to the print function. kwargs: Keyword arguments to pass to the print function.
""" """
if _LOG_LEVEL <= LogLevel.DEBUG: if _LOG_LEVEL <= LogLevel.DEBUG:
print(f"[blue]Debug: {msg}[/blue]", **kwargs) msg_ = f"[blue]Debug: {msg}[/blue]"
if progress := kwargs.pop("progress", None):
progress.console.print(msg_, **kwargs)
else:
print(msg_, **kwargs)
def info(msg: str, **kwargs): def info(msg: str, **kwargs):

View File

@ -25,7 +25,7 @@ from redis.asyncio import Redis
from reflex import constants, model from reflex import constants, model
from reflex.compiler import templates from reflex.compiler import templates
from reflex.config import Config, get_config from reflex.config import get_config
from reflex.utils import console, path_ops, processes from reflex.utils import console, path_ops, processes
@ -288,15 +288,7 @@ def initialize_web_directory():
path_ops.mkdir(constants.Dirs.WEB_ASSETS) path_ops.mkdir(constants.Dirs.WEB_ASSETS)
# update nextJS config based on rxConfig update_next_config()
next_config_file = os.path.join(constants.Dirs.WEB, constants.Next.CONFIG_FILE)
with open(next_config_file, "r") as file:
next_config = file.read()
next_config = update_next_config(next_config, get_config())
with open(next_config_file, "w") as file:
file.write(next_config)
# Initialize the reflex json file. # Initialize the reflex json file.
init_reflex_json() init_reflex_json()
@ -337,27 +329,34 @@ def init_reflex_json():
path_ops.update_json_file(constants.Reflex.JSON, reflex_json) path_ops.update_json_file(constants.Reflex.JSON, reflex_json)
def update_next_config(next_config: str, config: Config) -> str: def update_next_config(export=False):
"""Update Next.js config from Reflex config. Is its own function for testing. """Update Next.js config from Reflex config.
Args: Args:
next_config: Content of next.config.js. export: if the method run during reflex export.
config: A reflex Config object.
Returns:
The next_config updated from config.
""" """
next_config = re.sub( next_config_file = os.path.join(constants.Dirs.WEB, constants.Next.CONFIG_FILE)
"compress: (true|false)",
f'compress: {"true" if config.next_compression else "false"}', next_config = _update_next_config(get_config(), export=export)
next_config,
) with open(next_config_file, "w") as file:
next_config = re.sub( file.write(next_config)
'basePath: ".*?"', file.write("\n")
f'basePath: "{config.frontend_path or ""}"',
next_config,
) def _update_next_config(config, export=False):
return next_config next_config = {
"basePath": config.frontend_path or "",
"compress": config.next_compression,
"reactStrictMode": True,
"trailingSlash": True,
}
if export:
next_config["output"] = "export"
next_config["distDir"] = constants.Dirs.STATIC
next_config_json = re.sub(r'"([^"]+)"(?=:)', r"\1", json.dumps(next_config))
return f"module.exports = {next_config_json};"
def remove_existing_bun_installation(): def remove_existing_bun_installation():

View File

@ -193,12 +193,13 @@ def run_concurrently(*fns: Union[Callable, Tuple]) -> None:
pass pass
def stream_logs(message: str, process: subprocess.Popen): def stream_logs(message: str, process: subprocess.Popen, progress=None):
"""Stream the logs for a process. """Stream the logs for a process.
Args: Args:
message: The message to display. message: The message to display.
process: The process. process: The process.
progress: The ongoing progress bar if one is being used.
Yields: Yields:
The lines of the process output. The lines of the process output.
@ -209,11 +210,11 @@ def stream_logs(message: str, process: subprocess.Popen):
# Store the tail of the logs. # Store the tail of the logs.
logs = collections.deque(maxlen=512) logs = collections.deque(maxlen=512)
with process: with process:
console.debug(message) console.debug(message, progress=progress)
if process.stdout is None: if process.stdout is None:
return return
for line in process.stdout: for line in process.stdout:
console.debug(line, end="") console.debug(line, end="", progress=progress)
logs.append(line) logs.append(line)
yield line yield line
@ -260,7 +261,7 @@ def show_progress(message: str, process: subprocess.Popen, checkpoints: List[str
# Iterate over the process output. # Iterate over the process output.
with console.progress() as progress: with console.progress() as progress:
task = progress.add_task(f"{message}: ", total=len(checkpoints)) task = progress.add_task(f"{message}: ", total=len(checkpoints))
for line in stream_logs(message, process): for line in stream_logs(message, process, progress=progress):
# Check for special strings and update the progress bar. # Check for special strings and update the progress bar.
for special_string in checkpoints: for special_string in checkpoints:
if special_string in line: if special_string in line:

View File

@ -4,106 +4,56 @@ import pytest
from reflex import constants from reflex import constants
from reflex.config import Config from reflex.config import Config
from reflex.utils.prerequisites import initialize_requirements_txt, update_next_config from reflex.utils.prerequisites import _update_next_config, initialize_requirements_txt
@pytest.mark.parametrize( @pytest.mark.parametrize(
"template_next_config, reflex_config, expected_next_config", "config, export, expected_output",
[ [
( (
"""
module.exports = {
basePath: "",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};
""",
Config( Config(
app_name="test", app_name="test",
), ),
""" False,
module.exports = { 'module.exports = {basePath: "", compress: true, reactStrictMode: true, trailingSlash: true};',
basePath: "",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};
""",
), ),
( (
"""
module.exports = {
basePath: "",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};
""",
Config( Config(
app_name="test", app_name="test",
next_compression=False, next_compression=False,
), ),
""" False,
module.exports = { 'module.exports = {basePath: "", compress: false, reactStrictMode: true, trailingSlash: true};',
basePath: "",
compress: false,
reactStrictMode: true,
trailingSlash: true,
};
""",
), ),
( (
"""
module.exports = {
basePath: "",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};
""",
Config( Config(
app_name="test", app_name="test",
frontend_path="/test", frontend_path="/test",
), ),
""" False,
module.exports = { 'module.exports = {basePath: "/test", compress: true, reactStrictMode: true, trailingSlash: true};',
basePath: "/test",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};
""",
), ),
( (
"""
module.exports = {
basePath: "",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};
""",
Config( Config(
app_name="test", app_name="test",
frontend_path="/test", frontend_path="/test",
next_compression=False, next_compression=False,
), ),
""" False,
module.exports = { 'module.exports = {basePath: "/test", compress: false, reactStrictMode: true, trailingSlash: true};',
basePath: "/test", ),
compress: false, (
reactStrictMode: true, Config(
trailingSlash: true, app_name="test",
}; ),
""", True,
'module.exports = {basePath: "", compress: true, reactStrictMode: true, trailingSlash: true, output: "export", distDir: "_static"};',
), ),
], ],
) )
def test_update_next_config(template_next_config, reflex_config, expected_next_config): def test_update_next_config(config, export, expected_output):
assert ( output = _update_next_config(config, export=export)
update_next_config(template_next_config, reflex_config) == expected_next_config assert output == expected_output
)
def test_initialize_requirements_txt(mocker): def test_initialize_requirements_txt(mocker):