Next.jsのPreview modeでページのプレビューを表示する

Next.js v9.3でpreview modeが追加されました

CMSっぽいの作ろうと思ってるので、ちょっと試してみました

めちゃシンプルで簡単にPreviewできたのでメモ

ドキュメントは↓ nextjs.org

サンプルコードは↓ github.com

Preview modeのAPIを用意する

ヘッドレスCMDなどからプレビューページを表示するためのエンドポイントを作成します

ぼくのサンプルコードではこんな感じ

// /pages/api/preview.ts

export default (req, res) => {
  const id = req.query.id;

  res.setPreviewData({});
  res.writeHead(307, { Location: `/posts/${id}` });
  res.end();
};

クエリパラメータのidで対象のURLへリダイレクトしているだけです

setPreviewDataには任意のデータを渡せますが、cookieで保持しているのでサイズには上限があります

previewDataのリミットは2KBだそうです

Pagesでpreview mdoeの振る舞いを記述する

Preview modeだとgetStaticPropsのpropsにpreview: truepreviewDataL {}が渡ってきます

ぼくのサンプルコードではpreviewをcomponentに渡してラベルを表示するようにしました

// /pages/posts/[id].tsx

import { useRouter } from 'next/router';

export const getStaticPaths = async () => {
  return {
    paths: [{ params: { id: '1' } }],
    fallback: true,
  };
};

export const getStaticProps = async ({ params, preview }) => {
  const isPreview = preview || false;
  return { props: { id: params.id, previewMode: isPreview } };
};

const Post = ({ id, previewMode }) => {
  const router = useRouter();

  if (router.isFallback) {
    return <div>Loading...</div>;
  }

  return (
    <>
      {previewMode ? 'Preview mode' : ''}
      <h1>{`Page's ID is ${id}`}</h1>
    </>
  );
};

export default Post;

これだけです!

http://localhost:3000/api/preview?id=1にアクセスすると/pages/1にリダイレクトされて、Preview modeのラベルが表示されるはずです

f:id:murajun1978:20200525020216p:plain

Previewモードを解除する

Preview modeを解除するには、cookieをクリアしてあげればOKです

解除用のエンドポイントを作成します

// /pages/api/preview-disable.ts

export default (req, res) => {
  const location = req.headers.referer || '/';

  res.clearPreviewData();
  res.writeHead(307, { Location: location });
  res.end();
};

pageにも解除用のリンクを追加します

// /pages/posts/[id].tsx

import { useRouter } from 'next/router';

export const getStaticPaths = async () => {
  return {
    paths: [{ params: { id: '1' } }],
    fallback: true,
  };
};

export const getStaticProps = async ({ params, preview }) => {
  const isPreview = preview || false;
  return { props: { id: params.id, previewMode: isPreview } };
};

const Post = ({ id, previewMode }) => {
  const router = useRouter();

  if (router.isFallback) {
    return <div>Loading...</div>;
  }

  return (
    <>
      {previewMode ? 'Preview mode' : ''}
      <h1>{`Page's ID is ${id}`}</h1>
      {previewMode ? (
        <a href="/api/preview-disable">Disable preview mode</a>
      ) : null} // リンクを追加
    </>
  );
};

export default Post;

f:id:murajun1978:20200525020707p:plain

このリンクをクリックすると、posts/1にリダイレクトしてPreview modeのラベルやリンクが表示されないはずです

まとめ

すごくシンプルで簡単にPreviewを実装できました

実際の運用では、previewのデータを取得する実装が必要になりますね

公開されているデータを取得するclinetとPreviewのデータを取得するclientを作って、getStaticPropsで切り替えるとかね

Enjoy Next.js ヘ(^o^)ノ

Next.jsでTailwind CSSとEmotionを使う

Next.jsでTailwind CSSとEmotionを使える環境を作ってみます

$ npm init next-app --example with-tailwindcss-emotion [アプリケーション名]

create-next-appを使って、exampleをダウンロードするだけでOKです

Exampleのリポジトリ

github.com

これだけだとあっけないので、Tips的なものを1つ

[ご注意] ここからは↑で作成したexampleを前提に書いてます。他の環境では動作しないかもです

こんなコンポーネントがあったとします

/** @jsx jsx */
import { jsx } from '@emotion/react'
import tw from '@tailwindcssinjs/macro'

const styles = {
  button: tw`
    relative
  `
}

const Button = () => <button css={styles.button}>button</button>
export default Button

JSX Pragmaなるもの(/** @jsx jsx */)とimport { jsx } from '@emotion/react'を毎回かかないといけません。。。

しかも、ESLintにjsxno-unused-varsだと怒られたりもします。。。

そこで、@emotion/babel-preset-css-propですよ

emotion.sh

@emotion/babel-preset-css-propは、Emotion用パーサーのbabelプラグインです

導入手順

  • Installation
    • @emotion/core
      • @emotion/babel-preset-css-propには@emotion/coreが必要なのでインストール
  $ npm install @emotion/core
  
  or 
  
  $ yarn add @emotion/core
  • @emotion/babel-preset-css-prop
  $ npm install @emotion/babel-preset-css-prop
  
  or 
  
  $ yarn add @emotion/babel-preset-css-prop
  • Modify babel config
{
  "presets": [
    "next/babel",
    "@emotion/babel-preset-css-prop" # この行を追加
  ],
  "plugins": [
    "macros",
    "@emotion/babel-plugin"
  ]
}

これで ↓ の2行が不要になります

/** @jsx jsx */
import { jsx } from '@emotion/react'

やったね!

あ、TypeScriptだとcssってpropsの型しらねーって怒られるので、型定義も追加する必要があります

# emotion-env.d.ts (ファイル名はなんでもOK)
/// <reference types="@emotion/core"/>

Happy Tailwind CSS and Emotion ヘ(^o^)ノ

Railsのmodelでprivateメソッドのロジックをテストしない

みなさんはRailsのモデルでprivateメソッドをテストしますか?

僕はしません。

理由はpublicメソッドではないからです。

Privateメソッドをテストするって理由の一つが、「privateメソッドのロジックをテストしたい」だと思います。

ふーん、でもそのロジックはそのモデルにあるべきなんでしょうか?そして、そのモデルでテストするべきなのでしょうか?

他のモデルで同じロジックを使いたいときはどうするのでしょうか?

Railsにはconcernという機能がありますが、僕は基本使わなくなりました。

なせ?単純にいらないからですw

嘘です、必要です。そもそもconcernの存在意義が、ロジックを共通化するものではないというだけです。

では、どうするのか?

そのロジック用のモデルを作成することで解決できます。

具体的にやってみましょう。

Humanモデルにロジックを書いてみる

class Creature
  include ActiveModel::Model
  include AcitveMode::Attributes

  attribute :name, :string
end

class Human < Creature
  private

  def hello
    "Hello #{name}!"
  end

  def hi
    "Hi #{name}!"
  end
end

hellohi メソッドをテストしたいよね?

でも、privateメソッド、、

ならば!

Greetモデルを作成してみる

class Human < Creature
  private

  def greet
    @greet ||= Greet.new(creature: self)
  end
end
class Greet
  include ActiveModel::Model
  include AcitveMode::Attributes

  attribute :creature

  def hello
    "Hello #{creature&.name}!"
  end

  def hi
    "Hi #{creature&.name}!"
  end
end

どうでしょうか?

これでprivateメソッドのテストは不要になりますね。(Greetモデルのテストで担保するため)

さらに移譲してみましょう。

class Human < Creature
  delegate :hello, :hi, to: :greet

  private

  def greet
    @greet ||= Greet.new(creature: self)
  end
end
irb>human = Human.new(name: 'murajun1978')
irb>human.hello #=> "Hello murajun1978!"
irb>human.hi #=> "Hi murajun1978!"

みなさんも、新しいロジックがひょこっと出てきたら、新しいモデルを作成しましょう。

そのモデルをテストすることで品質も担保できますしね。

みなさんも、Railsでよきオブジェクト指向を満喫してください。

Apollo serverのSchema directivesを使ってdeprecation warningを表示する

Apollo serverでリファクタリングや仕様変更などで、特定のフィールドをdeprecatedにしたい場合があります

Apollo serverの @deprecated を使えば簡単にdeprecation warningを表示できます

では、やってみましょう

// src/index.js
const { ApolloServer, gql } = require("apollo-server");

const typeDefs = gql`
  type Author {
    name: String
  }

  type Book {
    title: String
    author: Author
    authorName: String
  }

  type Query {
    books: [Book]
  }
`;

const resolvers = {};
const server = new ApolloServer({
  typeDefs,
  mocks: true
});

server.listen(4001).then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

BookのauthorNameをdeprecatedにしたいので、deprecation warningを表示したいと思います

type Book {
  title: String
  author: Author
  authorName: String
    @deprecated(
      reason: "\`authorName\` is deprecated. Use \`author\` instead."
    )
}

これだけです!超簡単!

Playgroundで確認してみましょう!

f:id:murajun1978:20200311235454p:plain

DOCSもみてみましょう

f:id:murajun1978:20200311235619p:plain

ちゃんと表示されてますね!

schemaDirectivesは自分で定義することもできるので、特定のフィールドを表示するかを認可で切り替えとかもできますね

Happy GraphQL ヘ(^o^)ノ

RailsでYAMLに定義したカスタム設定を使う

Railsで自分で定義したYAMLファイルをロードしてアプリケーションで使いたい!

ってことでやってみますー

僕がRailsでカスタム設定といえば、そうこの子です!

github.com

Latest commit 0ae134b on Aug 7, 2014

なるほど、開発やめちゃったんだねー

そう言えばこんな子もいたよ

github.com

Latest commit 9a670d4 22 days ago

うん、大丈夫そう!これを使お、、う?

ちょっと気になったことが、

僕がゴリゴリRailsでアプリ作りまくってたときは、settingslogicはみんな使ってた

なのに、更新されてない???

なんで?

そこでRails本体のconfig周りを調べてみました!

すると、、

github.com

config_for って知らない子ですねー

どうやら、Railsのrootディレクトリ/config配下にあるYAMLファイルをシュッとロードしてくれるメソッドみたいです

# config/my_settings.yml

default: &default
  setting_name: My setting

development:
  <<: *default
irb(main):001:0> Rails.application.config_for(:my_settings)
=> {:setting_name=>"My setting"}

第2引数の env のデフォルトは Rails.env なので省略可能です

環境変数を使ってみましょう!

# config/my_settings.yml

default: &default
  setting_name: <%= ENV.fetch("MY_SETTING_NAME","") %>

development:
  <<: *default
irb(main):001:0> ENV["MY_SETTING_NAME"] = "My setting name"
irb(main):002:0> Rails.application.config_for(:my_settings)
=> {:setting_name=>"My setting name"}

settingslogicやconfigなどのgemがなくても、簡単にYAMLフィアルをロードできました!

Railsガイドにもちゃんと書かれてましたー

railsguides.jp

Markdown BlogをNext.jsで作る

はてなブログに書いてるけど、興味のある技術を試す場としてブログを自分でbuildしようかと思う

Markdownで記事を書くなら、GatsbyJSとかでさくっとできそうだけど、そこはあえて自分が興味あるもので作ってみる

Next.js + MDX + TypeScript でブログを構築していく

Next.js

Installation

$ npm install next react react-dom
$ npm install -D @types/node @types/react typescript

Add Index and Blog page

// src/pages/index.tsx
import Link from 'next/link';

const Index = () => (
  <div>
    <Link href="/blog">
      <a>Blog</a>
    </Link>
  </div>
);

export default Index;
// src/pages/blog.tsx
const Blog = () => (
  <div>
    <p>Welcome to my blog</p>
  </div>
);
export default Blog;

Add npm script

# package.json
{
  ...
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  }
}

Start Next.js

$ npm run dev

これでindexページとblogページが表示できるはず

僕はVSCodeを使ってdebugしたいので ↓ inspect用のスクリプトも用意している

# package.json
{
  ...
  "scripts": {
    "dev": "next",
    "dev:inspect": "NODE_OPTIONS='--inspect=0.0.0.0' next",
    "build": "next build",
    "start": "next start"
  }
}
# .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Docker: Next.js",
      "skipFiles": [
        "<node_internals>/**"
      ],
      "remoteRoot": "/home/app",
      "protocol": "inspector",
      "port": 9229
    }
  ]
}

MDX

最初は react-markdown + gray-matter で行こうと思ったけど、

MDX が良さそうだったので採用

MDXはMarkdownをJSXで使えちゃうし、導入も楽ちんだった!

Installation

$ npm install @next/mdx @mdx-js/loader
// next.config.js
const withMDX = require("@next/mdx")({
  extension: /\.mdx?$/
});

module.exports = withMDX({
  pageExtensions: ["ts", "tsx", "mdx"]
});
// mdx.d.ts
declare module '*.mdx' {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  let MDXComponent: (props: any) => JSX.Element;
  export default MDXComponent;
}
# tsconfig.json
{
  ...
  "include": [
    "mdx.d.ts", # 追加
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx"
  ]
}

Add Sample post

// src/posts/sample_post.mdx
export const meta = {
  title: 'Sample Post',
};

# Sample Post

## Welcome to My Blog
// src/pages/blog.tsx
import SamplePost from '../posts/sample_post.mdx';

const Blog = () => (
  <div>
    <SamplePost />
  </div>
);

export default Blog;

参考

f:id:murajun1978:20200210000411p:plain

これでmarkdownの記事を表示することができました{^^)

【現場で役立つシステム設計の原則】ドメインオブジェクトの見つけ方

現場で役立つシステム設計の原則をよみ直している

ドメインオブジェクトの見つけ方

かなり適当に言ってしまうと、会話だと思いました! (実際、そう書いてある)

この辺は僕もあやしくて、多分こんな感じーって実装してしまいます

ですが、優秀なエンジニアさんはめっちゃ聞きますw

業務でやってることや困ってることを聞いて、それを解決するアーキテクチャを決めてコードを書くって流れでしょうか

そう、いいプロダクトは会話からできると言っても過言ではないかもですねー

ま、人が使うものを人が作るわけで当たり前と言えば当たり前です

見つけ方ですが、ざっくり全体のフローを聞き取りしてから、手帳なりホワイトボードなりに書くのがよさそうです

プロジェクトチームから常に見えるところに、ドットペーパーに付箋を貼っておくのもいいかも

業務の関心ごとをヒト、モノ、コトに分類して整理する方法が紹介されています

業務を理解するときに使う図法も紹介されています

  • コンテキスト図
  • 業務フロー図
  • パッケージ図
  • クラス図

これらの図から、ドメインオブジェクトを見つけクラスにしていきます

そう、ドメインモデルは業務知識の塊なのです

なので、新たな業務が発見されると、必然的にクラスが追加され、そのクラスにはロジックが記述されます

そうして、業務知識を得ながらドメインモデルを育てていきます

もっとも大事だと感じたのは、クラス名やメソッド名ですね (ここも僕はいまいち自信がないw

業務で使っている言葉を、ドメインモデルに起こして、ロジック (処理) がメソッド名とマッチしていれば、業務知識(ドメイン)がコードに落ちている状態だと言えますね

うん、あとから参加したメンバーにもわかりやすく良さそうです

中途半端なドキュメントを保守するより、業務ドメインをコードに落とし込み、テストコードで仕様を確認できるとすばらし