コンテンツにスキップ
ブログに戻る

2018年2月5日(月)

Next.js 5: ユニバーサルWebpack、CSSインポート、プラグイン、ゾーン

投稿者

Next.js 5.0を全世界に紹介できることを大変嬉しく思います。npmで今すぐ利用可能です。アップグレードするには、以下を実行してください。

ターミナル
npm i next@latest react@latest react-dom@latest

Next.jsを更新するだけでなく、ピア依存関係の`react`と`react-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モジュール

最も要望の多かった機能の1つは、CSSファイルをインポートし、Webpackローダーを活用する機能です。

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

ユニバーサルWebpackによるCSSインポートを使用するページの例(`pages/index.js`)

これを機能させるには、必要なローダーをピア依存関係として含めることができます。

ターミナル
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.jsコミュニティがメンテナンスするためのnext-pluginsモノレポを公開しています。すべてのPRを歓迎します!

TypeScriptサポート

JavaScriptエコシステムで最も急速に成長しているテクノロジーの一つがTypeScriptです。そのため、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は最も要望の多かった機能の1つです。これを他のローダーと同じくらい簡単にプロジェクトに組み込めるように、`next.config.js`ファイルに含めることができる`next-typescript`プラグインを用意しました。

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

プラグインは簡単に構成できます。ただの関数だからです。

React代替ライブラリとモジュールオーバーロードのサポート強化

Reactの多くのドロップイン代替実装がこれまで登場しました。その中でも注目すべきものには、[preact](https://preact.dokyumento.jp/)、nervjsinfernoがあります。

他のライブラリは、ブラウザ互換性に若干のトレードオフを導入することでより小さなReactビルドを目指している`react-dom-lite`のように、DOMレンダラーの置き換えに焦点を当てています。

ユニバーサルWebpackのサポートにより、**これらのライブラリをドロップイン代替として組み込むプロセスがさらに簡単になります**。他のプラグインと同様に、preactでNext.jsを使用するために必要なのはこれだけです。

ターミナル
npm i @zeit/next-preact preact preact-compat

preactプラグインと必要なピア依存関係をインストールします

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

preactに対応した新しい`next.config.js`

非常にシンプルな@zeit/next-preactモジュールを確認するか、独自のモジュールを作成してください!

本番環境でのオプションの外部ソースマップ

Next.jsでWebpackがクライアントとサーバーの両方のコードに使用されるようになったため、本番ビルドでソースマップを有効にするには、その設定を少し調整するだけです。

開発環境ではソースマップが自動的に有効になるため、本番環境では異なる設定を行います

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

開発環境でない場合にのみ、`devtool`オプションを異なるように設定します。

ゾーン

Next.jsの当初からの目標の一つは、ウェブのシンプルさを取り戻し、維持することでした。

サーバーレンダリング、データフェッチに対するシンプルで不可知論的なアプローチ、ファイルシステム構造に基づいた宣言型ページは、この考え方に沿って導入した機能の一部です。

ウェブサービスやウェブサイトの、しばしば見過ごされがちな側面は、それらが*いかに自然に構成可能でスケーラブルであるか*ということです。

例えば、`mydomain.com/settings`と`mydomain.com/`は、完全に異なる2つのアプリである可能性があり、それぞれ独立してデプロイされ、独立してスケールされ、さらには同じソフトウェアの異なるバージョンを実行することさえ可能です。

エンドユーザーに統一されたエクスペリエンスを提供するために、それらを「接着」するために必要なのは、バックエンドのルーティング層またはそれらを世界に公開するロードバランサーのいくつかの簡単な設定だけです。私たちは、通常の`<Link>`コンポーネントを使用して連携する、**Next.jsで構築された複数のアプリケーションを構成する**機能を提供できることを大変嬉しく思います。この機能を**ゾーン**と呼んでいます。

例として、Vercelにデプロイされたこれら2つの独立したNext.jsアプリケーションを考えてみてください。

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内で変更があるたびに、私たちのボットが自動的にデプロイします。

結果的に、私たちのパスエイリアス機能を使用して親ドメイン`https://vercel.com`に統合された*2つのゾーン*ができました。それはこのような形をしています。

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

これらのシンプルなルールにより、マイクロサービスとゾーンを組み合わせて構成できます。

あとは、`now alias`コマンドを呼び出すだけです。

ターミナル
now alias -r rules.json my-domain.com

私たちの使命は、デプロイを可能な限りユニバーサルでオープンなものにすることです。ローカル開発を支援するため、私たちは最近、上記の同じ設定形式で動作するツールである`micro-proxy`をオープンソース化しました。

同様に、Nginx、HAProxy、API Gatewayなどの他のソリューションとゾーンを組み合わせることも可能です。

高速な本番ビルド時間

私たちは、開発者体験とユーザー体験は密接に関係していると考えています。変更が効率的に記述、テスト、デプロイされるほど、新機能の追加、バグの修正、そして全体的なユーザー体験の向上が加速します。

したがって、私たちはシステムの最も基本的な構成要素のパフォーマンスプロファイルについて、継続的に改善することに注力し続けます。

Next.js 5.0では、本番環境へのデプロイやNext.jsアプリケーションを静的サイトとしてエクスポートする前に実行するコマンドである`next build`を改めて見直す機会がありました。

数千のコンポーネントで構成されるReactアプリであるvercel.comにおいて、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

これは、デプロイ間で、ユーザーや顧客がすでに使用したコードを不必要に再ダウンロードする必要がなくなることを意味します。

フラグメント

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はソースマップをサポートしていないため、サーバー側で発生するエラーには、コンパイル済みコードを指すスタックトレースが伴います。

Next 5では、サーバー側のソースマップサポートを改善しました。サーバーレンダリング時に発生するエラーは、正しい関数と行番号を指すようになりました。

Errors now show the correct line, file and function name
エラーが正しい行、ファイル、関数名を表示するようになりました。

結論

ユニバーサルWebpackはNext.jsの基盤を強化し、将来性をさらに高めます。概して、Next.jsに適用できるプラグインとローダー、適用できないものの間に人工的な区別はもはやありません。

*ゼロコンフィギュレーション*の精神で、Next.jsの機能を自動的に拡張し、特定のつまみを調整する必要がないレシピのコミュニティリポジトリであるNextプラグインを導入できることを嬉しく思います。

これにより、追加のモジュールを導入し、`next.config.js`での含意を明確にするだけで、CSSソリューションの全範囲、TypeScriptのようなコンパイル・トゥ・JS言語、そしてNervのようなReact代替をサポートするようになりました。分かりにくさのないシンプルさです。

ゾーンは、同じリポジトリやサーバーに根ざしていないNext.jsアプリケーションを相互接続することを可能にします。私たちはこれを「チームのスケーラビリティ」という改善のカテゴリーにおいて、非常に重要なマイルストーンと考えています。

したがって、Next.jsは複数のチームによってメンテナンスされる大規模アプリケーションにとって素晴らしい選択肢となります。彼らは今、改善を並行してデプロイし、エラー発生面を減らし、イテレーション速度を高め、さらには状態管理やデータフェッチに対するさまざまなアプローチのように、私たちのコアに加えて異なるテクノロジーを試すことさえできるようになります。

この機会に、この機能の設計につながる重要な洞察、コード、テストに貢献してくださったDeep Varma氏とTruliaエンジニアリングチームに感謝申し上げます。

いつものことながら、このリリースは多くのオープンソース貢献者と素晴らしいコミュニティなしには実現できませんでした。