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,
reactStrictMode: true,
trailingSlash: true,
output: "",
};

View File

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

View File

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

View File

@ -677,7 +677,7 @@ class AppHarnessProd(AppHarness):
frontend_server: Optional[Subdir404TCPServer] = None
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 = {
404: web_root / "404.html",
}

View File

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

View File

@ -45,7 +45,11 @@ def debug(msg: str, **kwargs):
kwargs: Keyword arguments to pass to the print function.
"""
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):

View File

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

View File

@ -193,12 +193,13 @@ def run_concurrently(*fns: Union[Callable, Tuple]) -> None:
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.
Args:
message: The message to display.
process: The process.
progress: The ongoing progress bar if one is being used.
Yields:
The lines of the process output.
@ -209,11 +210,11 @@ def stream_logs(message: str, process: subprocess.Popen):
# Store the tail of the logs.
logs = collections.deque(maxlen=512)
with process:
console.debug(message)
console.debug(message, progress=progress)
if process.stdout is None:
return
for line in process.stdout:
console.debug(line, end="")
console.debug(line, end="", progress=progress)
logs.append(line)
yield line
@ -260,7 +261,7 @@ def show_progress(message: str, process: subprocess.Popen, checkpoints: List[str
# Iterate over the process output.
with console.progress() as progress:
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.
for special_string in checkpoints:
if special_string in line:

View File

@ -4,106 +4,56 @@ import pytest
from reflex import constants
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(
"template_next_config, reflex_config, expected_next_config",
"config, export, expected_output",
[
(
"""
module.exports = {
basePath: "",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};
""",
Config(
app_name="test",
),
"""
module.exports = {
basePath: "",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};
""",
False,
'module.exports = {basePath: "", compress: true, reactStrictMode: true, trailingSlash: true};',
),
(
"""
module.exports = {
basePath: "",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};
""",
Config(
app_name="test",
next_compression=False,
),
"""
module.exports = {
basePath: "",
compress: false,
reactStrictMode: true,
trailingSlash: true,
};
""",
False,
'module.exports = {basePath: "", compress: false, reactStrictMode: true, trailingSlash: true};',
),
(
"""
module.exports = {
basePath: "",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};
""",
Config(
app_name="test",
frontend_path="/test",
),
"""
module.exports = {
basePath: "/test",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};
""",
False,
'module.exports = {basePath: "/test", compress: true, reactStrictMode: true, trailingSlash: true};',
),
(
"""
module.exports = {
basePath: "",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};
""",
Config(
app_name="test",
frontend_path="/test",
next_compression=False,
),
"""
module.exports = {
basePath: "/test",
compress: false,
reactStrictMode: true,
trailingSlash: true,
};
""",
False,
'module.exports = {basePath: "/test", compress: false, reactStrictMode: true, trailingSlash: true};',
),
(
Config(
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):
assert (
update_next_config(template_next_config, reflex_config) == expected_next_config
)
def test_update_next_config(config, export, expected_output):
output = _update_next_config(config, export=export)
assert output == expected_output
def test_initialize_requirements_txt(mocker):