Nx×Next.js×Storybook×Emotion環境を用意する
前提
Nxを使っているので、少しベースがある状態でスタート
ベース
const rootMain = require('../../../.storybook/main');
const path = require('path');
module.exports = {
...rootMain,
core: { ...rootMain.core, builder: 'webpack5' },
stories: [
...rootMain.stories,
'../components/**/*.stories.mdx',
'../components/**/*.stories.@(js|jsx|ts|tsx)',
],
addons: [
...rootMain.addons,
'@nrwl/react/plugins/storybook',
'storybook-addon-swc',
{
name: 'storybook-addon-next',
options: {
nextConfigPath: path.resolve(__dirname, '../next.config.js'),
},
},
],
webpackFinal: async (config, { configType }) => {
// apply any global webpack configs that might have been specified in .storybook/main.js
if (rootMain.webpackFinal) {
config = await rootMain.webpackFinal(config, { configType });
}
// add your own webpack tweaks if needed
return config;
},
};
まずはこのまま起動
動くには動くけど、emotionは入ってないのでもちろんスタイルは当たらない
よくある要件だと思うので、Nxのプラグイン@nrwl/react/plugins/storybook
が何かやってくれないかなーと思ってリポジトリを散策
なんかemotion呼び出してる
じゃあなんでemotion適応されないの?
// Check whether the project .babelrc uses @emotion/babel-plugin. There's currently
// a Storybook issue (https://github.com/storybookjs/storybook/issues/13277) which apparently
// doesn't work with@emotion/*
>= v11
// this is a workaround to fix that
んー
プロジェクトの.babelrc
をみてemotionのbabel-pluginを使っているかどうか見ているよう。。
悲しい。Next.jsをswcにあげちゃいました。
.babelrcがプロジェクトルートにあると、Next.jsがbabelコンパイルしちゃうので困る。。
options.babelrcPath
があるのでもしかしたら.storybookにbabelrcおいたらいけるかも
{
"presets": [
[
"next/babel",
{
"preset-react": {
"runtime": "automatic",
"importSource": "@emotion/react"
}
}
]
],
"plugins": ["@emotion/babel-plugin"]
}
読み込まれたっぽいけど、まだスタイルは当たらない
よくコードを読んでみると、emotionをimportしている部分でaliasを貼っているだけっぽいし、プラグインをstorybookのwebpackConfigで読み込む処理とかは書いてなさそう
こんな感じでemotion/babel-pluginが呼び出されているか見てみる
const rootMain = require('../../../.storybook/main');
const path = require('path');
module.exports = {
...
babel: async (options) => {
console.log(options);
return {
...options,
};
},
...
};
結果
入ってるんですけど、、
{
cacheDirectory: '...l',
presets: [ [ 'next/babel', [Object] ] ],
plugins: [ '@emotion/babel-plugin' ],
babelrc: false,
overrides: [
{ test: /\.(story|stories).*$/, plugins: [Array] },
{ test: /\.(mjs|jsx?)$/, plugins: [Array] }
]
}
css={}
で呼び出している場合は@emotion/babel-preset-css-prop
が必要そう
babel: async () => {
}
の中で上書きする方法もあると思うけど、なぜか動かなかったので、webpackFinalの中でやった
webpackFinal: async (config, { configType }) => {
// apply any global webpack configs that might have been specified in .storybook/main.js
if (rootMain.webpackFinal) {
config = await rootMain.webpackFinal(config, { configType });
}
config.module.rules.push({
test: /\.(ts|tsx)$/,
loader: 'babel-loader',
options: {
presets: [
['react-app', { flow: false, typescript: true }],
'@emotion/babel-preset-css-prop',
],
},
});
...
残念ながら、この辺りをやってくれるpluginはstorybookにはなさそうだったのでこれで妥協
上でEmotionは動くようになったけど、すでにあるStorybookファイルを噛ませてみると
React is not defined
が出てくる
Next.jsはv17?ぐらいのときからReactを暗黙的に挿入してくれている(何か、babelのプラグインがあったはず、、
swcの場合も入れてくれているので、Next.jsをしている分にはいらないのだが、StorybookはただReactを起動させてるだけだと思うのでこれも解消しないと
storybook-addon-swcでswcコンパイルしているみたいだし、Next.jsが使ってるswcコンパイラープラグインを噛ませればいけるのでは?
いや、Emotionをくっつける時に、webpackFinalでtsxのコンパイラーをbabel-loaderにしている時点でダメなのか?わからない。
一旦babel-loaderにreact importのやつを噛ませれないか検討
Storybook公式の見解はこれ
- Adding import React from 'react' to your component files.
- Adding a .babelrc that includes babel-plugin-react-require
コードに影響を及ぼしたくないので、2を試してみる
こんな感じでやったらいけた
config.module.rules.push({
test: /\.(ts|tsx)$/,
loader: 'babel-loader',
options: {
presets: [
['react-app', { flow: false, typescript: true }],
'@emotion/babel-preset-css-prop',
],
plugins: ['react-require'],
},
});
よく見たら、めちゃくちゃデフォルトのローダーを無視したコードだな。
babel: async () => {
}
で動かないのは、デフォルトの挙動と競合したからか?
またどこかのタイミングで検証(swcに全部載せ替えるチャレンジもしてみたい(多分rust書かないといけない。。
いけたいけたと思ってたら、画像が表示されていなかった
storybook-addon-nextがやってくれると思ってたけど、設定が足りてなかったかも
<imgタグには変換してくれてそうなので、srcかな
を読むと、ローカルイメージ(pngとかをそのままimportする形式)とリモートイメージ(srcに相対or絶対パスを書く形式)に対応している
とだけ書かれている
相対パスで書かれている時は、Next.jsが起動していないと見れないのでは?
Next.jsを起動してみる
→ 表示されない
あー、、
basePathだ、、普段はimage-loaderを噛ませてbasePathがつくようにしているけど、image-loaderはnext.config.jsで設定していて、これが読み込まれてなさそう
nextjs storybook addonがやってくれないのかなぁと思って見に行ったけど、下記issueやdiscusstionで話している。
未解決っぽい
なんならこの画像をうまくパースしてくれるものがほしくてこのaddonを使ってるまであるので、こうなってくると使う必要があまりなくなってくる。。
なので、一旦、storybook-addon-next
をなくしてみて実装してみる
前提
basePathがついているが、各Next/Imageを呼び出すコンポーネントでloaderを呼び出すのは保守性が下がりそうなので、customLoaderでくっつけている
Next/Imageをラップしたコンポーネントを作ると言う手もあったし、今となってはそっちの方が良かった気もするが
を使って、webpackのコンパイル中にNext/ImageをラップしたImageにすることで解消している
悲しいけど、一旦storybook-addon-nextをコメントアウト
const rootMain = require('../../../.storybook/main');
const path = require('path');
module.exports = {
...rootMain,
core: { ...rootMain.core, builder: 'webpack5' },
stories: [
...rootMain.stories,
'../components/**/*.stories.mdx',
'../components/**/*.stories.@(js|jsx|ts|tsx)',
],
addons: [
...rootMain.addons,
'@nrwl/react/plugins/storybook',
'storybook-addon-swc',
// ここをコメントアウト
// {
// name: 'storybook-addon-next',
// options: {
// nextConfigPath: path.resolve(__dirname, '../next.config.js'),
// },
// },
],
webpackFinal: async (config, { configType }) => {
// apply any global webpack configs that might have been specified in .storybook/main.js
if (rootMain.webpackFinal) {
config = await rootMain.webpackFinal(config, { configType });
}
// add your own webpack tweaks if needed
return config;
},
};
webpackFinalで
- next-image-loaderライブラリのNext/Imageを差し込み(aliasを差し替え)
- entryにimage-loaderのconfigファイルを追加し、読み込ませる
const rootMain = require('../../../.storybook/main');
const path = require('path');
module.exports = {
...rootMain,
core: { ...rootMain.core, builder: 'webpack5' },
stories: [
...rootMain.stories,
'../components/**/*.stories.mdx',
'../components/**/*.stories.@(js|jsx|ts|tsx)',
],
addons: [
...rootMain.addons,
'@nrwl/react/plugins/storybook',
'storybook-addon-swc',
// ここをコメントアウト
// {
// name: 'storybook-addon-next',
// options: {
// nextConfigPath: path.resolve(__dirname, '../next.config.js'),
// },
// },
],
webpackFinal: async (config, { configType }) => {
// apply any global webpack configs that might have been specified in .storybook/main.js
if (rootMain.webpackFinal) {
config = await rootMain.webpackFinal(config, { configType });
}
// add your own webpack tweaks if needed
config.resolve.alias['next/image'] = 'next-image-loader/build/image';
config.resolve.alias['next/legacy/image'] =
'next-image-loader/build/legacy/image';
config.entry.push(path.resolve(__dirname, './image-loader.config.js'));
return config;
},
};
import { imageLoader } from 'next-image-loader/build/image-loader';
// (resolverProps: { src: string; width: number; quality?: number }) => string
imageLoader.loader = ({ src, width, quality }) => {
return `${
http://localhost:4200/moving/_next/image?url=/moving${src}&w=${width}&q=${quality || 75}`;
};
これで画像も表示できた
あとはローカルだけ見てもらってもCI上とかで困るので、環境変数を流し込む必要がありそう
Opps..
環境変数の読み取りは、storybook-addon-nextがやってくれていたらしい
↑のissueにあるようにNX_の環境変数をzshrcとかに書けばいけるんだろうけど、一人一人の開発環境にそれを強制するのは厳しいので、他の解決策を考える
まぁ、CI上では環境変数はあると思うので、あまり気にせず、
import { imageLoader } from 'next-image-loader/build/image-loader';
const baseUrl = process.env.NEXT_PUBLIC_SERVICE_HOST ?? 'http://localhost:4200';
// write self-define a custom loader
// (resolverProps: { src: string; width: number; quality?: number }) => string
imageLoader.loader = ({ src, width, quality }) => {
return `${baseUrl}/moving/_next/image?url=/moving${src}&w=${width}&q=${
quality || 75
}`;
};
こんな感じで対応