跳到内容
返回博客

2018 年 2 月 5 日,星期一

Next.js 5:通用 Webpack、CSS 导入、插件和 Zones

发布者

我们非常高兴向世界介绍 Next.js 5.0。它已在 npm 上发布,立即生效。要升级,请运行

终端
npm i next@latest react@latest react-dom@latest

除了升级 Next.js 之外,我们还升级了对等依赖项 reactreact-dom

Next.js 是用于通用、服务器渲染(或静态预渲染)React.js 应用程序的工具包。开始开发任何规模的应用程序都像执行 next 一样简单。(阅读更多。)

每次发布新版本时,我们都致力于保持向后兼容性,提供简单的升级路径,并且仅在绝对必要时才进行 API 更改。Next.js 5.0 也不例外。

然而,在底层,Next.js 经历了彻底的改造,以实现强大的新用例和可扩展性。我们首先让 Next.js 为服务器和客户端代码共享通用的 Webpack 管道。

通用 Webpack 和 Next 插件

Next.js 利用了 Webpack、Babel 和 Uglify 等现有的强大工具,以非常简单的界面呈现给最终用户:next(用于开发)、next build(用于准备生产)和 next start(用于服务)或 next export(用于预渲染为静态文件)。

我们早期做出的决定之一是为这些工具的配置方式提供非常强大的扩展点。我们不仅想要易用性,还希望能够灵活地扩展工具包,满足您的任何需求。

例如,您可以通过在 next.config.js 中设置 webpack 属性来扩展 Next.js webpack 配置

由于 webpack 在生产和开发环境中的执行方式不同,因此我们当时决定将其设为一个函数,用于装饰我们的默认 webpack 配置

next.config.js
module.exports = {
  webpack(config, { dev }) {
    // modify it!
    return config;
  },
};

可选的 next.config.js 文件示例

但是,Webpack 仅在客户端(浏览器)捆绑包上执行,您将错过将这个出色的工具链用于服务器端渲染的可能性。

我们很高兴地宣布,我们已经广泛重构了我们的代码库,以使 Webpack 通用化

从您的角度来看,所有更改只是将额外的 isServer 属性传递给上面的装饰器函数。但是,新的语义意味着现在您可以使用广泛的 Webpack 加载器生态系统。

CSS、LESS、SASS、SCSS 和 CSS Modules

我们最受用户欢迎的功能之一是能够导入 CSS 文件并利用 Webpack 加载器

import './index.css';
 
export default function Index() {
  return (
    <div>
      <p>I love CSS!</p>
    </div>
  );
}

一个示例页面 (pages/index.js) 使用 CSS 导入,这要归功于通用 Webpack

为了使这项工作正常进行,您可以将您需要的加载器作为对等依赖项引入

终端
npm i --save css-loader style-loader postcss-loader

Next.js 让您可以自由选择您需要的加载器,并随意将其升级到不同的版本。

然后扩展配置以配置您的加载器。在 next.config.js

next.config.js
module.exports = {
  webpack(config, options) {
    const { dev, isServer } = options;
    const extractCSSPlugin = new ExtractTextPlugin({
      filename: 'static/style.css',
      disable: dev,
    });
    config.module.rules.push({
      test: /\\.css$/,
      use: cssLoaderConfig(extractCSSPlugin, {
        cssModules,
        dev,
        isServer,
      }),
    });
    return config;
  },
};

扩展原始 webpack 配置为您提供了极大的灵活性和控制权

虽然我们的一般建议是使用组件本地样式解决方案,例如内置的 styled-jsx babel 插件,但我们相信 CSS 加载器具有许多重要的优势,例如可以轻松重用现有的 CSS 代码库,并大大简化将旧代码库迁移到 Next.js 的过程。

我们没有默认启用所有可以想象到的功能和加载器,而是引入了Next.js 插件,它们是装饰您的配置的简单函数。您无需像上面那样手动扩展配置来设置加载器,只需执行以下操作即可

const withCss = require('next-css');
module.exports = withCss({
  /* extra optional config */
});

启用导入 .css 文件只需引入 next-css

阅读更多关于 Next.JS 的 CSS 加载器 用法,或参考我们已经为您创建的一些包

加载器
CSSnext-css
LESSnext-less
SASSnext-sass

我们的目标是授权社区开发和发展实用的简单扩展生态系统。为此,我们开放了 next-plugins monorepo,供 Next.js 社区维护。欢迎所有 PR!

TypeScript 支持

TypeScript 是 JavaScript 生态系统中增长最快的技术之一。以至于它正成为 Babel 7 正式支持的技术,这意味着它将通过自定义您的 .babelrc自然而然地受到 Next.js 的支持。

与此同时,得益于我们新的通用 Webpack 支持,您现在就可以获得完整的 TypeScript 支持!

您可以像这样扩展您的 webpack 配置

next.config.js
module.exports = {
  webpack(config, options) {
    const { dir, defaultLoaders } = options;
    config.resolve.extensions.push('.ts', '.tsx');
    config.module.rules.push({
      test: /\\.+(ts|tsx)$/,
      include: [dir],
      exclude: /node_modules/,
      use: [
        defaultLoaders.babel,
        { loader: 'ts-loader', options: { transpileOnly: true } },
      ],
    });
    return config;
  },
};

我们所要做的就是启用 ts-loader

与 CSS 加载器和预处理器一样,TypeScript 一直是最受欢迎的功能之一。为了使其像其他任何加载器一样容易地集成到项目中,我们现在有一个 next-typescript 插件,您可以将其包含在您的 next.config.js 文件中

next.config.js
const withTs = require('next-typescript');
module.exports = withTs({
  /* additional config*/
});

插件可以很容易地组合:它们只是函数

更好地支持 React Altlibs 和模块重载

随着时间的推移,涌现出了许多 React 的替代实现。其中,一些值得注意的有 [preact](https://preact.reactjs.ac.cn/)、nervjsinferno

其他库专注于替换 DOM 渲染器,例如 react-dom-lite,它旨在通过在浏览器兼容性方面进行一些小的权衡来制作更小的 React 构建版本。

通用 Webpack 支持使将这些库作为即插即用替代品集成变得更加容易。与其他插件一样,这就是您使用 Preact 版本的 Next.js 所需要做的全部操作

终端
npm i @zeit/next-preact preact preact-compat

我们安装 preact 插件和必要的对等依赖项

const withPreact = require('@zeit/next-preact');
module.exports = withPreact();

我们新的 next.config.js 已为 preact 做好准备

查看非常简单的 @zeit/next-preact 模块或创建您自己的模块!

生产环境中可选的外部 Sourcemaps

现在 Next.js 的客户端和服务器代码都使用 webpack,在生产构建中启用 source-maps 只是对其配置进行一个小小的调整。

在开发环境中,source map 会自动启用,因此我们在生产环境中对其进行不同的配置

next.config.js
module.exports = {
  webpack(config, { dev }) {
    if (!dev) {
      config.devtool = 'source-map';
    }
    return config;
  },
};

当不在开发环境中时,我们只需配置不同的 devtool 选项

Zones

从一开始,Next.js 的既定目标之一就是恢复并保留 Web 的简洁性。

服务器端渲染、用于数据获取的简单且与技术无关的方法以及基于文件系统结构的声明式页面,是我们根据这种想法引入的一些功能。

Web 服务和网站经常被忽视的一个方面是它们天然的可组合性和可扩展性

例如,mydomain.com/settingsmydomain.com/ 可能是两个完全不同的应用程序,它们可以独立部署、独立扩展,甚至可以运行同一软件的不同版本。

将它们“粘合在一起”,为最终用户带来统一体验所需的全部操作是对后端路由层或负载均衡器进行一些简单的配置,以将它们暴露给世界。我们现在非常高兴能够带来组合使用 Next.js 构建的多个应用程序的能力,这些应用程序使用普通的 <Link> 组件连接在一起。我们将此功能称为 Zones

例如,考虑这两个独立的 Next.js 应用程序,它们部署到 Vercel

Both of our pages have a seamless experience, but they belong to separate apps
我们的页面都具有无缝的体验,但它们属于不同的应用程序

当我们改进文档时,我们希望尽可能简化接受社区贡献的过程。

我们决定将文档“迷你网站”拆分到其自己的存储库中。此外,每当提交拉取请求并提出更改时,我们都会自动隔离部署它

Every time a change happens inside a PR our bot automatically deploys it
每次 PR 内部发生更改时,我们的机器人都会自动部署它

我们最终得到的是两个 zones,它们通过我们的路径别名功能组合到父域名 https://vercel.com 中。它看起来像这样

{
  "rules": [
    { "pathname": "/docs", "dest": "our-docs.vercel.app" },
    { "pathname": "/api", "dest": "our-docs.vercel.app" },
    { "dest": "my-main-website.vercel.app" }
  ]
}

这些简单的规则允许您将微服务和 zones 组合在一起

剩下的就是调用 now alias 命令

终端
now alias -r rules.json my-domain.com

我们的使命是使部署尽可能通用和开放。为了帮助本地开发,我们最近开源了 micro-proxy,这是一个使用与上面看到的相同配置格式的工具。

您也可以使用 Nginx、HAProxy 或 API Gateway 等其他解决方案将 zones 连接在一起。

更快的生产构建时间

我们认为开发者体验和用户体验是息息相关的。编写、测试和部署更改的效率越高,添加新功能、修复错误和改进整体用户体验的速度就越快。

因此,我们仍然专注于不断迭代系统最基本构建块的性能配置文件。

借助 Next.js 5.0,我们有机会再次审视 next build,即您在部署到生产环境或将 Next.js 应用程序导出为静态站点之前运行的命令。

我们很高兴地报告,对于 vercel.com,一个由数千个组件组成的 React 应用程序,我们在 Next.js 5.0 中看到了非常显著的改进,生产构建时间缩短了 23.6%

Our main application production build now takes 38 fewer seconds to complete
我们的主应用程序生产构建现在完成时间缩短了 38 秒

改进了动态导入的缓存

每当您使用动态 import() 时,这都会向 WebPack 发出信号,表明存在新的代码拆分入口点。

在构建时,这意味着为模块的相应子树生成特定的捆绑包。

在 Next.js 5.0 之前,动态捆绑包将收到一个类似于以下的 URL

/_next/1517592683901/webpack/chunks/components_hello1_1345d10fc951cd6717c5676c467579a6.js

现在,我们将动态导入转换为子树内容的内容可寻址哈希值

/_next/webpack/chunks/components_hello1_1345d10fc951cd6717c5676c467579a6-b7874680a9e21fb6eb89.js

这意味着在跨部署时,您的用户和客户无需不必要地重新下载他们已经使用过的代码。

Fragments

Next.js 构建了一个顶级的 <Document> 组件,该组件会与每个页面一起进行服务器端渲染。重载此组件使您可以完全控制您的标记,从而实现许多高级用例

初始标记的一部分是 Next.js 需要在客户端评估的脚本列表。自定义 _document 如下所示

pages/_document.js
import Document, { Head, Main, NextScript } from 'next/document';
export default class extends Document {
  render() {
    return (
      <html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

Document 允许您自定义页面的整个服务器端渲染输出

直到最近,我们还被迫将我们的脚本包装在 <div> 中。

借助 Next.js 5.0,我们现在可以利用新的 Fragment 支持,这转化为更轻量级的页面和对页面样式的完全控制,而没有多余的标记。

更准确的错误

Node.js 不支持 source map,服务器端发生的错误会附带指向已编译代码的堆栈跟踪。

借助 Next 5,我们改进了服务器端的 source map 支持。服务器端渲染时发生的错误现在指向正确的函数和行号。

Errors now show the correct line, file and function name
错误现在显示正确的行、文件和函数名称

结论

通用 Webpack 巩固了 Next.js 的基础,使其更具前瞻性。总的来说,不再有人为地分离哪些插件或加载器适用于 Next.js,哪些不适用。

本着零配置的精神,我们很高兴推出 Next 插件,这是一个社区存储库,用于自动扩展 Next.js 功能的方案,而无需调整特定的旋钮。

有了这个,我们现在仅通过引入一个额外的模块并在 next.config.js 中显式包含它,就支持 CSS 解决方案的整个范围、编译为 js 的语言(如 TypeScript)和 React 替代方案(如 Nerve)。简洁而不晦涩。

Zones 允许互连并非扎根于同一存储库甚至服务器的 Next.js 应用程序。我们认为这是“团队可扩展性”类别改进中非常重要的里程碑。

因此,Next.js 成为了由多个团队维护的大型应用的理想选择。他们现在可以同时部署改进,减少错误面,提高迭代速度,甚至可以在核心技术之外尝试不同的技术,例如多种不同的状态管理或数据获取方法

我们想借此机会感谢 Deep Varma 和 Trulia 工程团队,感谢他们贡献了关键的见解、代码和测试,从而促成了此功能的设计。

与往常一样,如果没有众多开源贡献者和我们出色的社区,这个版本的发布是不可能实现的。