RAKSUL TechBlog

ラクスルグループのエンジニアが技術トピックを発信するブログです

ROSCAFE TECH NIGHT #2 イベントレポート: CTO岸野が語るプラットフォーム開発と技術負債の本質

こんにちは、ラクスル広報の和田です。
11月21日(火)、ROSCAFE TECH NIGHT #2がラクスル目黒オフィスで開催されました。
このイベントには、ラクスルからCTO岸野が登壇。ほか、SODAさん、LinQさん、出前館さんのCTO/VPoEが一同に会し、テーマ「CTOやVPoEに求められる役割とは?」に基づいて熱いLTセッションが繰り広げられました。
本記事では、CTO岸野がLTで語った内容に焦点を当て、自身がどのようにしてCTOの地位に到達したのか、日常業務で直面する課題や現在の注力テーマについて、イベントの概要と共に紹介していきます。

ROSCAFE とは?

エンジニア専門のフリーランスエージェント「ROSCA」が運営するコミュニティの名称。このイベントでは、様々なプレイヤーが集まり、エンジニアリングの進化やこれからの展望について情報交換が行われています。

イベント概要

このイベントでは、時代の変化に応じてCTOやVPoEに要求される役割やリーダーシップの多様性に焦点を当てました。各登壇者は、組織の課題、解決方法、そして自身の役割の定義について、それぞれの視点でLTを行いました。

登壇者紹介

岸野 友輔(ラクスル株式会社 ラクスル事業本部CTO)
林 雅也(株式会社SODA 執行役員 CTO)
Kazuma Takeda(株式会社LinQ CTO)
米山 輝一(株式会社出前館 執行役員 IT本部 本部長 兼 VPoE)

LTセッション  (スピーカー:ラクスルCTO岸野)

CTOになった経緯

2017年、ラクスルに新卒第一号として採用されました。入社以来、印刷事業の中心である印刷のECサイトにおいて、表側の部分や裏側のシステム開発を担当し、パートナー様向けの印刷依頼システムの開発などに従事してきました。
また、プロジェクトとして印刷の発注を効率化するアルゴリズムの開発など、効果的なプロジェクトの立ち上げも行っていました。2021年には、ラクスルとして初めてのM&Aを実施し、段ボール製造の会社を傘下に迎えました。
プロジェクトやM&Aを通じた組織の発展に貢献し、2022年8月にダンボールワン社のCTOに就任。その後、2023年8月にはラクスルとの吸収合併により、現在はラクスル事業のCTOとして、統合後の組織の立ち上げ、開発スピードの向上、事業推進を担っています。

事業フェーズの変遷

印刷のEC事業からスタートし、急速な事業拡大と新規事業を展開してきました。特に、グッズのカスタマイズ可能なECサイトやアパレル・ユニフォーム事業の立ち上げなど、多岐にわたる挑戦を行ってきました。しかし、それに伴いシステムの技術的な課題が浮き彫りになり、技術負債が蓄積されていました。
当時、そのような状況を改善するための判断として採用されたのは、既存のシステムの拡張ではなく、新しいシステムの構築でした。この意思決定は正しかったと考えています。そうしたことで、柔軟性のある構造が作り上げられ、ラクスルは着実に成長しています。
現在は新たなフェーズに突入し、個別最適から中長期を見据えた全体最適への移行が進行しています。特に、全社的なプラットフォームの構築に注力し、購入者の利便性向上に焦点を当てています。
具体的なプロジェクトとして、ラクスルで購入したユーザーにとって非効率的だったECサイトの購買体験を向上させるための取り組みが進行中です。これにより、マイページや注文履歴の統合など、より統一されたユーザーエクスペリエンスが提供されることが期待されています。

開発組織のリーダー体制の変化

事業部を超えたアサイン変更の難しさやCTOへの一極集中が、事業運営において健全でない状態を招いていました。
その後、積極的な変革を行うため、新たな組織体制を導入しました。具体的には、Tech、 Bizの意思決定者を集約したTech意思決定機関の組成や、縦割りの個別最適から横軸での全体最適への転換です。新しい構造では、事業責任者やシステム部門、CTOなどが連携しやすくなり、迅速な意思決定が可能となりました。縦割りの組織から解放されたことで、より効率的で柔軟な開発体制が構築されたと思います。

現在のビジネスフェーズや組織の変遷を考慮すると、事業も進化する中でプラットフォームの構築が鍵となり、全体的な開発投資が不可欠です。
CTOは組織の目指すべき姿を描き、その推進をリードするリーダーシップが求められています。
同時に、技術を駆使して事業改造を促進し、レバレッジ可能なポイントを見つけ出すことが必要です。CTOがこれらの役割を果たすことで、攻めの要素を強調した役割に変容していると考えています。
新卒入社した当時から高めてきた事業解像度や事業変革プロジェクトのリーダーシップ経験を活かし、技術戦略を策定し、組織を牽引しています。

現在の注力テーマ

現在の主要な取り組みとして、私の注力テーマは大きく3つです。プラットフォームプロジェクトの推進、技術負債の解消計画の策定、そして開発組織の強化です。

1つ目のプラットフォームプロジェクトでは、成長を促進するために新規事業やM&Aなどの戦略を組み合わせ、状態を整えることで更なる発展を目指しています。

2つ目は、技術負債の返済計画の策定です。弊社のECシステムは事業において重要な位置を占めています。そのため、このコアなシステムにかかる技術的な負債に対処する必要があります。CTOとして、この技術的な側面を正確に認識し、適切な説明を通じて計画を進め、返済を推進することが求められています。 技術負債の返済計画の立案はCTOの役割の一環と考え、その計画をどう策定し、実行していくかについても具体的な取り組みを行っています。
特に、システムの注意深い監視と改善が不可欠で、実際にシステム量の見かけの変動に惑わされず、減少していく過程を明確に示すことが重要です。 技術負債の返済は挑戦的なプロジェクトであり、これをゼロに近づけるためには、計画をしっかりと実行する必要があり、そのための体制を整えています。

3つ目は、開発組織の強化に焦点を当て、計画の実現に必要な人材を育成・採用しています。具体的には、採用活動の強化、既存のメンバーがより活躍できるように、キャリアパスを整備し、個別のメンバーに焦点を当てた育成計画などが進行中です。これにより、計画の実現に必要な開発組織を構築し、全体の推進力を向上させています。

まとめ

本日改めて、ほかのLTも聞いていて感じたことは、CTOの役割は曖昧でありながらシンプルだということです。CTOは経営の中で最もテクノロジーを理解し、テクノロジー組織の中で経営も理解している存在として、必要なことをシンプルに進め、自らが最も得意とする分野で最も力を発揮できる場所に焦点を当て、柔軟性を持って変化することが重要です。今後も、確かなテクノロジー理解を持ち、成長に貢献していきたいと思います。

懇親会

LTセッション終了後の懇親会では、参加者と登壇企業のCTOやVPoEの皆さんがアットホームな雰囲気で交流し、興味深い話題を共有しながら、今後のテクノロジー業界やリーダーシップに対する期待などについて熱く語り合う場となりました。

おわりに

ROSCAFE TECH NIGHTに初めて参加させていただきましたが、技術やリーダーシップに関する情報交換が盛んで、カジュアルな雰囲気でイベントを楽しむことができました。
異なるバックグラウンドを持つ企業同士の交流も、やっぱりおもしろい!
ご用意いただいた軽食もマックとあり、ハンバーガー片手に交流をしている様子はカジュアルさを引き立てていました(笑)
参加いただいた皆さま、登壇者・関係者の皆さま、そして何より企画運営をいただいたRoscaの皆さま、ありがとうございました!
また、イベントでご一緒できる日を楽しみにしています!

ノバセル 内定者インターン4人による座談会: なぜ僕たちはラクスルを選んだか

こんにちは! 今回、24新卒としてラクスルグループのノバセル株式会社に配属予定のエンジニア4人が、選考やインターンを通じてラクスルについて感じたことをパネルディスカッション形式で話しました! ラクスルに興味のある人が気になりそうなことをテーマにみんなで意見を持ち寄り4人でディスカッションしました。

ビデオ会議ツールで通話を行う4人
パネルディスカッション時の筆者4人
(左上から時計回りで新開・秦・石川・小谷)

筆者紹介

秦健心
愛知工業大学大学院経営情報科学部修了予定。 内定者インターンとして、ノバセルでデータエンジニアとしてデータ基盤整備を行なっている。 在学中には情報系サークルで会長を務めた。また、ハッカソンなどに複数参加しWebアプリのチーム開発を行ってきた。

小谷雄大
大阪大学大学院工学研究科を卒業予定。 大学では、機械学習を用いた原子スケールのシミュレーションの研究を行っている。 内定者インターンではデータサイエンティストとして統計解析から分析パッケージの内製化、MLOps基盤の構築を担当している。

新開崇弘
電気通信大学大学院情報理工学研究科を修了予定。 大学では人間の姿勢に関してリアルタイムにフィードバックを行うシステムを開発し、人間に対してどのように情報を伝えるかといったインタフェースの研究を行っている。 在学中はWeb制作だけでなく、営業職やマーケティングエンジニアなどの幅広い職種に挑戦し、インターンでは受賞やチームリーダーの経験がある。 内定者インターンとしてサーバサイドエンジニアを担当。

石川ナディーム
東京工科大学を卒業予定。 学生生活では、企業様との分散処理に関する共同研究、ハッカソンやデータ分析コンペ、英語でミュージカルを行う学生団体での活動など幅広く興味を持ったものにチャレンジしました。 内定者インターンではデータサイエンティストとして社内向けの業務改善ツールの開発を行っている。 サッカーが好き。最近はラテアートにハマっている。

パネルディスカッション開始

なぜラクスルを選んだのか

秦:

面接でだいぶ本性を出して話した上で自分の良いところも悪いところも受け入れてくれたし、面接やインターンで自分らしくいながら貢献できそうだと感じられたから。会社が持つ雰囲気や文化にマッチしていると感じたから。

小谷:

産業構造を仕組みで変えるという壮大な挑戦に加わりたかった。 データサイエンティストとして入社する上でノバセルはデータが事業のコアとなっており働きがいがあると感じたから

新開:

フロントエンド・バックエンドなどの領域に縛られずに幅広いことに挑戦できそうだったから。

石川:

マーケティングの民主化というビジョンに共感したのと、面接を担当してくださった方々が例外なくいい人だったから。

この質問に対しては、それぞれの回答に共感はしつつも、それぞれが見てるところはちょっとずつ違うんだなと感じましたね。 ディスカッションでは、意見や質問を発言をしやすいフラットな環境や、フルスタックエンジニア的な技術に限らずさまざまなことへの挑戦できる環境などの環境に対する魅力、ビジョンへの共感やデータ事業に対する魅力などもありましたね。 CTOの前職が機械学習チームの所属だったため、CTOの機械学習に対するリテラシーが高いこともデータサイエンティストとしては嬉しいとのことでした。 あとは、選考における面接やワークサンプルでの相互理解している感じも良かったと盛り上がりました。

23卒の選考についての座談会を行なっている昨年の記事がありますのでぜひご覧ください! ワークサンプルがどの様なものだったかの話もしています!!

recruit.raksul.com

ラクスルの良いところは?

秦:

文化。 問題や課題の原因を人に求めず、仕組みで解決しようとするマインドを持っているところ。 皆が自分の意思を持った上で皆のために働けている。(もしかしたらノバセルのいいところかも?)

小谷:

自分の意見を言いやすい環境であり、それが組織として推奨されているところ 新しい技術に対する感度も高く積極的に取り入れようとするところ

新開:

雰囲気の良さ。自分のペースで仕事を進められるように感じる。 1on1などでフィードバックを貰い修正しながら作業できる。

石川:

風通しがいい、議論が活発、人がいい

この質問では文化に対する良いところが結構出ましたね。 週一出社でほぼリモートな環境ながら、Gatherや発言文化の明言化でお互いが話しかけやすい雰囲気を作れているのは良いですね。 (上司やCTOにも気兼ねなく話せる) あとは、技術に対する感度ですね。 Rustの採用や、ChatGPTやGitHub Copilotの利用が導入されていたりと生産性に対する感度の高さも魅力的という意見がありましたね。 ビジネスの面でも、ラクスルという土台からさらにノバセル、ハコベル、ジョーシスといった事業を伸ばせてるのも良い点かなと思います。

質問や意見がしやすいか

秦:

めちゃくちゃしやすい。 分からないせいで軽んじられたり馬鹿にしたりといった雰囲気は全くない。 Working Out Loudという文化があり、思ったことを口に出しやすい雰囲気もある。

小谷:

しにくいと感じたことがない

新開:

意見のしにくさを感じることは一切ない。むしろ「議論を重ねる中で必要な知識を得て、一人で自走できるエンジニアに近づけると思うので頑張りたい」と伝えたところ歓迎された。

石川:

風通しがよく、質問はしやすい雰囲気。なんでもウェルカムな雰囲気は感じる。

これまでの質問でも出ていましたが、みんな共通して質問しやすい雰囲気を感じてます。 出た意見として「上司が部下に質問する環境だからこそ質問がしやすい」という意見がありました。 「上司が部下に質問しない」と部下ばかりが上司の時間を使ってる様な気がして質問できなくなってしまうらしいです。 ラクスルは「分からないことはちゃんと聞こう」ってことをみんながしているので質問しやすくなっているのかなと思いました。

コーディングする時間とそれ以外の時間配分は?

秦:

インターン中なのでなんともと言うところもある。 けど、コーディングだけでなく、組織文化について考えたり、ビジネスのことについて考える時間をちゃんと割いている人が多いなと言う印象。 コードのことだけを考えている人は少ないかも。 自分はコードを書くだけでなく勉強する時間も業務内で行えている。

小谷:

時期によるが、半々くらい(コーディング以外はドキュメント読んだり、データの前処理したりなど)

新開:

(今のところ)50:50くらい?1on1などの振り返りのほかに、コーディング前に設計に必要な知識をインプットしたりどのような設計で開発するかを議論する時間もある。

石川:

人によるところがあるから、難しい。自分はコーディング6割、残りは技術についてのリサーチだったり、今だったら業界知識のインプットをしていたりします。

エンジニアでも職種やロールによって違うが、コーディング以外にも時間を割いています。(エンジニアリングマネージャー、データサイエンティスト、データエンジニア、Webエンジニアなど様々) 主にプロダクト理解やビジネスへの理解、コミニュケーションなどは共通して発生すると思います。 あとは、個人のやりたいこと(Will)にも左右されますね。 コーディングしたい人はコーディング時間を増やそうと思えば増やせるし、コーディング以外のビジネスや組織文化について取り組む時間を増やしたい人は増やすことができますね。

ワークライフバランスはどうか?

秦:

だいぶ良いと思う。 11:00~16:00のコアタイムさえいればいつ働いても良いフレックス制なので、自分に割く時間は作りやすい。 また、用事があればコアタイム中であっても抜けたりしても許容される環境っぽい。(同じチームの人を見た感じ)

小谷:

基本的に残業時間は少なく、休みはあるため特に不自由はない

新開:

暦通り(土日祝が休み)の働き方。また基本コアタイム制での稼働だが、予定がある場合はそれに限らない。

石川:

週一出社が基本で、オフィスに行く頻度がちょうどいい。フルリモートも理想的だが、新卒で入る会社は、人間関係構築も含めて週一出社がちょうど良いかなと個人的には思う。 残業に関しては、基本しなくても良さそうな雰囲気で、してる人はどちらかというとポジティブな理由、例えば新しい技術を学んだり、自分のスキルアップをするために残業をしているイメージ。強制的に残業はあんまりないと思う。

みんな共通してますけど、だいぶ働きやすいと思いますよ。 フレックスタイム制なので、働く時間に関してもだいぶ自由が効きますし、コアタイム内でも通院などの都合があれば中抜けなどもできます。 会社自体が社員のワークライフバランスを尊重してくれてる感じがあるので、そこはありがたいですね。 ほぼリモートながらいつ出社しても良いのも魅力ですね。

人間関係は良好か?

秦:

めちゃ良好だと思う。 人間関係を良くするための文化がいくつか定義されているのもあるし、いる人たちがそもそも協調して働く意識をちゃんともっていると思う。 さらに、ただ気を使ってるわけじゃなくて、必要なことをお互い言い合えていると思うし、お互いフィードバックを受け入れる姿勢もしっかりしているなと思う。

小谷:

良好

新開:

人間関係が悪くなっている人を見たことがない。かなり良好だと思う。

石川:

新卒に関しては、全体の人数自体が高校とかの1クラス分だったりするので、仲良くなりやすい規模感 縦のつながりに関しては、新卒だから〇〇とかは一切無く、新卒でも議論の中心になったりするような感じ。オープンな人が多いからこそ、議論に参加しやすい

みんな共通して良好だと感じてるみたいです。 選考の中でラクスルの文化へのマッチ度が重視された結果、「みんなで協力したいよね」「それぞれの考えを尊重すべきだよね」みたいな考えに共感する人が集まっているのは仲の良さの理由の一つかもしれません。 ラクスル内で新卒向けのイベントを開いてくれたりしたおかげで、同期仲もまだ入社してないにも関わらずかなり良好です。 (社内イベント後に同期だけで飲みにいったりしました)

自分がやりたいことが実現できる環境か?

秦:

まだ自分のやりたいことが決まりきっていないような気がするが、実現できる環境だと思う。 ミクロな視点で見ると会社にとって必要なことであれば、自分でタスクを掴みに行くことができる環境だし、マクロな視点でもエンジニアという枠を超えて自分のやりたいことに近いキャリアを進んで行くことができる環境だなと思う。

小谷:

1on1などで自分のwillの話をするが業務などもそれに沿ったものをやるため実現できそう。

新開:

様々なエンジニア領域を触れてたくさんのことを経験できそう。さらに1on1で目標に対して成長の角度が適切かを定期的に振り返ることができる。

石川:

マーケティング・データ・統計学に触れられる環境ですごい魅力的だし、やっていて楽しい

ここは、今までの質問でも少し触れられていましたけど、皆共通してできるという認識ですね。 willが尊重してもらえるからこそ、willを自分自身が大事にもって公言していくのが大事そうです。 また「20%ルール」や「斧を研げ」といった社内文化やTriNovasellという社内制度でタスクだけに押しつぶされるのではなく、やりたいことに挑戦できる環境ですね。 (TriNovasellは月一で3日間をフルに使って業務改善や調査ができる仕組み)

最後に

最後に就活生の方々に向けて筆者それぞれからメッセージです。

秦:

会社を選ぶ上でいろんな軸があると思いますが、自分の中で何を大事にしたいのかを言語化してほしいなと思います。その上で、ビジョンへの共感、一緒に働く人、働く環境あたりが大事だなと思った人にはラクスルおすすめなのでぜひ選考を受けてみてください!

小谷: 

ラクスルはテクノロジーで業界構造ごと変えるチャレンジができる魅力的な環境です。 入社後は難しい課題に取り組むことが多くあると思いますが、果敢に挑戦していきます。

新開:

ラクスルのビジョンに共感できて、様々な挑戦をしたい方にはラクスルはとても良い企業だと思います。 皆さんと共に切磋琢磨できることを楽しみにしています!自分も頑張ります!!

石川:

「仕組みを変えれば、世界はもっと良くなる」、エンジニアを目指している方なら、日常生活の中でも感じたことがあるのではないでしょうか。ラクスルでは実際に業界構造という大規模なものから社内業務までたくさんの仕組みを絶えず変えていて、これからもより良くしていこうという姿勢を、まだ内定者インターンの身ですが感じています。エンジニアとしても、とてもチャレンジングで刺激的な現場です。みなさんと、どこかでお会いできるのを楽しみにしています!

25卒向け新卒採用始まっています! ぜひお申し込みください!!

hrmos.co

ダンボールワンのフロントエンドでゼロランタイムCSS in JSを採用してみた

ダンボールワンのフロントエンドでゼロランタイムCSS in JSを採用してみた

こんにちは、ラクスルの宮崎(@miyahkun)です。現在はラクスル株式会社の「ダンボールワン」という、ダンボールや梱包資材を扱うサービスの運用・開発を行っています。我々のチームではNext.jsを用いたサービス開発をしています。今回はその中で採用したvanilla-extractというツールについて紹介します。

背景と課題

我々がこのプロジェクトを始めた当初は、ラクスルでの採用実績が長いCSS Modulesの導入を検討していました。Next.jsのドキュメントで最初に記載されているのがCSS Modulesであり、当時は強く推奨されていたことを覚えています。しかし、Next.jsの環境構築を進めていく中で、他のプロジェクトで利用していたPostCSSのcustom mediaというpluginがうまく動作しないことに気づきました。PostCSSのメジャーバージョンアップ(v7 → v8)に伴い、PostCSS plugin周りに入った大きな変更が原因のようです。

いくつか代替案はありましたが、1からのプロジェクトという折角の機会なので、ラクスルでは導入していない新しいCSSツールにも視野を広げてみようという話になりました。

技術選定の要件

CSSツールの技術選定をするにあたって、求める要件は以下の通りです。

  • エディター上での開発体験:クラス名やプロパティの補完が効くこと
  • React Server Component対応:当時はまだexperimentalでしたが、Next.js App Routerの採用を見据えて、React Server Component上でも動作すること(現在はApp Routerを利用しています)
  • パフォーマンス:ブラウザランタイム上での動作が不要なもの。また、CDNによるCSSファイルのキャッシュが可能なもの
  • 未使用のスタイル定義が追える:長期運用していく上で、使われているか分からないCSSを増やさないため

これらの要件を満たす候補を探してチームで議論した結果、選ばれたのはvanilla-extractでした。

vanilla-extractとは

vanilla-extractがどういうものか簡単に紹介します。

vanilla-extract.style

一般的なCSS in JSライブラリと同様にJavaScriptを用いてスタイルを記述する一方で、ビルド時にはCSSファイルを出力します。

ファイル名は .css.ts のような命名規則に従い、パッケージから提供されているstyle 関数を利用します。JavaScriptのオブジェクトとしてCSSプロパティを記述します。

// hoge.css.ts
import { style } from '@vanilla-extract/css';

export const container = style({
  display: 'flex',
  padding: 10,
});

コンポーネント内では以下のようにスタイルを適用します。

// SomeComponent.tsx
import * as styles from './hoge.css';

export const SomeComponent = () => (
  <div className={styles.container}>
    // some contents here...
  </div>
);

ビルド時にはバンドラーによりCSSファイルとして出力されます。

満足している点

補完による実装スピードの向上

ラクスルではVue.js SFC + CSS Modulesを採用しているプロダクトが多いため、補完が効きづらく書きづらいなと個人的に感じていました。vanilla-extractでは純粋なTypeScriptとして型が付くため、IDEの拡張機能は不要です。内部的にはcsstypeが利用されておりCSSプロパティの補完も効くため、実装スピードもかなり早くなりました。また、TypeScriptのオブジェクトとして正確に参照が追えるため、定義元ジャンプを簡単にできる点も嬉しいです。

参照の追跡により安全にCSSを削除できる

私は普段VSCodeを利用していますが、”TypeScript > References Code Lens” をオンにすると参照カウントが表示されます。スタイルを書きながら、消し忘れているスタイルに気付きやすいので非常に便利です。(どこで使われているか分からない迷子CSSをビクビクしながら削除する作業とはおさらばです 👋)

未使用のCSSの削除に関してはPurgeCSSなどのライブラリでも可能ですが、TypeScriptの参照を追えた方がより確実だろうと思っています。

VSCodeの設定画面で「TypeScript, Reference Code Lens」機能にチェックを入れている

vanilla-extractを用いたスタイル定義行の上部に「1 reference」という参照カウントが表示されている

また、プロジェクト全体で参照されていないコードを見つけるために、CLIとして実行できるts-pruneも活用しています。vanilla-extractに限らず、TypeScriptファイル全体で未使用のコードがないかチェックできます。我々のチームでは気が向いたらローカル環境で実行する程度のゆるい運用にとどめています。

活用方法やTipsなど

最小限の機能のみ利用

vanilla-extractにはいくつかのパッケージが提供されています。SprinklesはTailwindCSSのようなユーティリティ形式の定義をサポートし、 Recipesはランタイム上でスタイルを切り替えるAPIを提供しています。導入した当初はこういった機能を活用した方が良いのだろうかと悩みましたが、基本的なAPIの利用だけで十分実装できています。ダンボールワンのECサイトではダークモード対応や、デザインルールが明確に定まっていないことも、シンプルな実装になっている要因かと思います。大抵のことはミニマムに始めるのが吉ですね 👍

サイト全体で利用するカスタムプロパティの定義

サイト全体で利用するカラーバリエーションやフォントサイズ、z-indexなどをカスタムプロパティとして定義することが多いと思います。vanilla-extractでは createGlobalTheme というAPIを活用するのがおすすめです。例えば、セレクター :root に対してカスタムプロパティを定義したい場合は以下のように書きます。

import { createGlobalTheme } from '@vanilla-extract/css';

export const color = {
  gray100: '#fafafa',
  // ...
  gray700: '#391f0d',
} as const;

export const vars = createGlobalTheme(':root', {
  color,
  // other variables...
});

ビルド結果は --color-gray100__yahcht2 のようにハッシュが付き、名前の衝突が起きない仕組みになっています。

固定文字を含んだセレクターが欲しいとき

vanilla-extract は style 関数を利用するとセレクターにハッシュが自動で付与されます。しかし、外部のライブラリなどと連携するために決められたセレクターを生成したい場合があります。具体例の一つとしてreact-transition-groupがあげられます。このライブラリはアニメーションの開始・途中・終了などのタイミングで適用されるセレクターは xxx-enterxxx-enter-active のようにsuffixが決められています。このような場面では style 関数と globalStyle 関数を組み合わせると良いです。以下はポップアップのUIでアニメーションを適用する例です。

import { globalStyle, style } from '@vanilla-extract/css';

export const popup = style({});

globalStyle(`${popup}-enter`, {
  opacity: 0,
  transform: 'translate3d(0, -6px, 1px)',
  display: 'block',
});

globalStyle(`${popup}-enter-active`, {
  opacity: 1,
  transition: `opacity 300ms ease-in-out, transform 300ms ease-in-out`,
});
// BasePopup.tsx
import { CSSTransition } from 'react-transition-group';
import * as styles from './BasePopup.css';

export const BasePopup = ({ children }) => {
  return (
    <CSSTransition classNames={styles.popup}>
      <div>{children}</div>
    </CSSTransition>
  );
};

popup という空のセレクターを作成し、globalStyle 関数の第一引数で使用している点が肝です。 popupstyle 関数によりハッシュ付きのセレクターとなっています。一方、globalStyle 関数は第一引数で指定された文字列がそのままセレクターになります。これにより、ハッシュによるスコープ化を効かせながら、固定のsuffixを持つセレクターを定義することができます。

導入してから気づいた点

半年ほどvanilla-extractを利用してきた中で気づいたことを2つ紹介します。

親コンポーネントから子コンポーネントのスタイルを上書きできないときがある

親コンポーネントから子コンポーネントのスタイルを上書きしようとしたときに、意図した通りに上書きしてくれないことがあります。そもそも、コンポーネントのスタイルを外部から上書きするようなことがあまり推奨されないかもしれませんが、時々そういった場面は訪れます。

// child.css.ts
const text = style({
  color: 'red'
});

// child.tsx
const ChildComponent = ({ className }) => (
  <div className={clsx(className, styles.text)}>This text can be red...</div>
);

// parent.css.ts
const textFromParent = style({
  color: 'blue'
});

// parent.tsx
const ParentComponent = () => (
  <ChildComponent className={styles.textFromParent} />
);

CSSの定義順がバンドラー依存のため、意図しないスタイルが適用されてしまうことがあります。私は他のCSSツールを利用してきた中でこのような問題に出会ったことはありませんでした。しかし、バンドラーを通してCSS定義の順番を決定する仕組み上、潜在的に抱えている問題のようです。

対策として、以下のようなことを心がけています。

  • CSSで上書きするようなスタイル設計を避ける
  • コンポーネントの props として外部から指定し、上書きが発生しないようにする
  • 最悪の場合、!important を指定する(今のところ出番はありません 😊)

プロパティの記述順序をソートできない

vanilla-extractではStylelintのプラグインが存在しないため、Stylelintエコシステムの恩恵を受けることができません。一番影響が大きいのはCSSプロパティの記述順をソートできなくなったことです。これに関しては、チーム内でどのような順序を良しとするかの認識がある程度揃っているため、大きな問題にはなっていません。後述するLinariaというCSS in JSライブラリでは公式プラグインが提供されているので、vanilla-extractにも提供されてほしいなと思います。いくつかのIssueではvanilla-extractの構文の関係で難しいとのコメントも見かけました。完璧ではなくとも、部分的に自動整形してくれるようになれば嬉しいですね(OSSコミットチャンス!)

採用しなかった技術

チーム内で候補に上がったものの採用されなかったCSSツールの技術を一部紹介します。どんな観点で技術選定しているかの参考になれば嬉しいです。

CSS Modules + typed-css-modules

github.com

typed-css-modulesはCSSファイルからTypeScriptの型定義を自動で生成してくれるライブラリです。純粋なCSS記法で書きつつ、型補完も効くため学習コストゼロで書き始められます。冒頭で述べたメディアクエリ周りの問題がなければこちらが最も有力な候補でした。

TailwindCSS

tailwindcss.com

“utility-first CSS framework” と呼ばれるような、事前に定義されたセレクターを組み合わせてスタイルを適用する方式です。人気のあるツールですが、以下の理由から我々には向いていないと判断しました。

ダンボールワンのECサイトはデザインルールが明確に定まっていません。例えば、marginの大きさや文字サイズはUIごとに細かく調整しており、事前に決められたサイズ(デザイントークン)の中から組み合わせるようなデザインにはなっていません。TailwindCSSを使いこなすためには、デザイントークン + それを元に定義したTailwindCSSのutilityを厳密に運用することが不可欠だと思っています。しかしながら、現状のチーム体制でそこまでやり切ることは難しいと判断しました。

Linaria

linaria.dev

タグ関数を用いたテンプレートリテラルでスタイル定義します。テンプレート内は素のCSSと同じ記法のため、既存のCSS実装を移行するのは簡単そうです。一方で、テンプレート記法のため、シンタックスハイライトがエディターの拡張機能に依存します。vanilla-extractとは立ち位置がかなり似ていると思います。当時はありませんでしたが、現在ではStylelintのプラグインが公式で提供されているのは地味に嬉しいポイントです。

まとめ

今回はラクスルの「ダンボールワン」開発チームで導入したvanilla-extractについて紹介しました。ダンボールワンではモダンな技術を取り入れたプロダクト開発を行っています。直近ではNext.js App RouterやChromaticなどの知見を共有していく予定です。ぜひ楽しみにしていてください!

HonoとDenoで社内ツールを作ってみた

こんにちは!ラクスルの灰原です! 軽量かつ高速なWebフレームワークであるHonoと、新進気鋭のJSランタイムであるDenoを使って、社内ツールを作ってみましたので紹介します。

作ったツール

テックブログ向けのアイキャッチ画像ジェネレータを作りました。

タイトルを入力して、 背景画像と文字色を選んで、 文字の位置と大きさを調整して、 後は「Download」ボタンを押せば画像が手に入ります。

これは以前、弊社デザイン組織で作られた「Zoom背景ジェネレータ」に多分に影響されています。 こちらのデザイナーブログも是非ご覧ください! note.com

技術スタック

このツールは利用頻度がそこまで多くないと思われることもあり、お手軽にローカルで動くアプリとして作ることにしました。 またプロダクションではなく社内ツールということもあり、モダンな技術を取り入れて作ってみました。 最終的な技術スタックは以下の通りです。

  • Hono
    • 軽量・高速で各種JSランタイムに対応したWebフレームワーク
  • Deno
    • 次世代JSランタイム
  • esbuild
    • Goで実装された高速なバンドラー
  • Twind
    • tailwind-in-js を実現するJSライブラリ
  • Alpine.js
    • 動きのあるページを手軽に作れるJSフレームワーク

Honoのコンセプトについて知りたい方は、作者のyusukebeさんのブログもご覧ください。 zenn.dev

ファイル構造はこのようになっています。

.
├── Dockerfile
├── README.md
├── public
│   └── assets
│       └── images # 背景画像
│           ├── ... 
│           ├── ...
│           └── ...
└── src
    ├── components # JSXコンポーネント
    │   ├── footer.tsx
    │   ├── header.tsx
    │   ├── layout.tsx
    │   └── main.tsx
    ├── scripts # ブラウザ上で動かすスクリプト
    │   ├── app.js
    │   └── canvas.js
    ├── index.tsx # アプリのエントリーポイント
    ├── twind.ts # twindを使うためのスクリプト
    └── esbuild.ts # esbuildを使うためのスクリプト

全体の行数カウントは429行です。なかなかコンパクトに作ることができました。

goclocの実行結果

index.tsx の内容

アプリのエントリーポイントである index.tsx に関してもコンパクトに書くことができました。 index.tsx ではHonoで配信する内容を設定して、最後に Deno.serve をしています。各行を見ていきましょう。

まずはHono本体と関連のコンポーネントをimportします。

/** @jsx jsx */
import { Hono } from "https://deno.land/x/hono@v3.9.2/mod.ts";
import {
  jsx,
  logger,
  poweredBy,
  serveStatic,
} from "https://deno.land/x/hono@v3.9.2/middleware.ts";

Twindやesbuildを使うためのスクリプトをimportします。これらはHonoのMiddlewareという形で実装されています。こちらについては後述します。 Middleware - Hono

import { tailwindStyleTagInjector } from "./twind.ts";
import { withForms } from "https://esm.sh/@twind/forms@0.1.4";
import presetTailwind from "https://esm.sh/@twind/preset-tailwind@1.1.4";

import { esbuildBundler } from "./esbuild.ts";

次に各種JSXコンポーネントをimportします。HonoはJSXを使うことができます! JSX - Hono

import { Layout } from "./components/layout.tsx";
import { Header } from "./components/header.tsx";
import { Footer } from "./components/footer.tsx";
import { Main } from "./components/main.tsx";

importが終わり、アプリの設定をしていきます。ここではアプリの初期化、各種Middlewareの設定をしています。

const app = new Hono();

app.use("*", logger(), poweredBy());
app.use("/public/assets/*", serveStatic({ root: "./" }));
app.use(
  "*",
  tailwindStyleTagInjector({
    presets: [
      presetTailwind(),
      {
        theme: {
          preflight: withForms(),
        },
      },
    ],
  })
);

app.get(
  "/scripts/*",
  await esbuildBundler({
    entryPoints: ["./src/scripts/app.js"],
    outdir: "/scripts/",
  })
);

最後に先ほどimportしたJSXコンポーネントを組み合わせて、ルートパスで返すHTMLを指定し、サーバを立ち上げます。

app.get("/", (c) => {
  return c.html(
    <Layout>
      <Header />
      <Main />
      <Footer />
    </Layout>
  );
});

Deno.serve(app.fetch);

以上が index.tsx の内容です。シンプルですよね!

Tips

HonoやDenoを使ってみて、いくつかTipsを得られたので紹介します。

HonoでTwindを使う

TwindをSSRで使う際、返却されるHTMLを読み込ませて、そこから適切なstyleタグが生成され、それを元のHTMLに埋め込んで配信するというような動きになります。 Use With: React • Twind.style

つまり、Honoがレスポンスを返す直前で処理をフックして、Twindを使ってレスポンスのbodyを書き換える必要があります。 これはHonoのMifflewareを作ることで実現しました。

HonoのContextからbodyの内容を取得し、Twindの inline メソッドでstyleタグを挿入済みのHTMLを生成しています。

  • twind.ts
import { createMiddleware } from "https://deno.land/x/hono@v3.9.2/helper.ts";
import { inline, install } from "https://esm.sh/@twind/core@1.1.3";

// response bodyにTailwindを使うために必要なstyleタグを挿入する
// see: https://twind.style/packages/@twind/core#inline
export const tailwindStyleTagInjector = (config: any) => {
  install(config);

  return createMiddleware(async (c, next) => {
    await next();

    if (!c.res.body) {
      return;
    }

    const stream = c.res.body.pipeThrough(new TextDecoderStream());
    const buffer: string[] = [];

    for await (const chunk of stream) {
      buffer.push(chunk);
    }

    const html = buffer.join();
    const inserted_html = inline(html);

    c.res = new Response(inserted_html, c.res);
  });
};

HonoのJSXでAlpine.jsを使う

HonoのJSXの中で、Alpine.jsを使った :src="thumbnail" のような動的なアトリビュートの割り当てを記述すると、下記のようなエラーになってしまいます。

error: The module's source code could not be parsed: Unexpected token `button`. Expected jsx identifier at xxx

これはHonoのJSXとhtmlヘルパーを組み合わせることで解決できます。以下のようにJSXのなかにhtmlヘルパーの記述を埋め込むことで、エラーにならずにレンダリングすることができます。

const Component = () => (
  <button>
    {html`
      <img :src="thumbnail" />
    `}
  </button>
);

今回Honoを使ってみて、このJSXとhtmlヘルパーの組み合わせがとても体験が良いと感じました。HonoのJSXについてはyusukebeさんのこちらのブログでも紹介されています。 zenn.dev

Hono+Denoでesbuildを使う

このアプリはブラウザ上で動くスクリプトがAlpine.jsに依存していますが、実はCDNもNodeも使っていません。

実際のスクリプトをご覧ください。そうです、Alpine.jsのインポートにDenoのnpm:specifierを使っています!

import Alpine from "npm:alpinejs";
import { setupFunctions } from "./canvas.js";

window.Alpine = Alpine;
Alpine.start();

setupFunctions();

これはDeno公式のWebフレームワークであるFreshの機能を真似たものです。

Fresh 1.2 – welcoming a full-time maintainer, sharing state between islands, limited npm support, and more

npm: specifireによるimportはもともとDenoの機能で、Denoでネイティブにnpmパッケージを扱うためのものです。 これを使うことで node_modules が生成されず、Denoのキャッシュにパッケージが保持されます。

npm: specifiers | Deno Docs

さて、これはDenoランタイム上の挙動、つまりサーバーサイドでの挙動です。 Freshではそれをブラウザ上で動かすフロントエンドのコードでも使えるようにしています。

esbuild_deno_loaderというesbuildのプラグインで、それが実現されています。esbuildによるバンドルをする際、npm: specifierの記述があれば、node_modulesではなくDenoのキャッシュを参照するようになっているようです。

feat: support for `npm:` specifiers by lucacasonato · Pull Request #67 · lucacasonato/esbuild_deno_loader · GitHub

このesbuild_deno_loaderとesbuildを使ったバンドルを、HonoのMiddlewareとして実装することで、npm: specifierで依存先を記述したフロントエンドのコードを動かしています。

  • esbuild.ts
import { createMiddleware } from "https://deno.land/x/hono@v3.9.2/helper.ts";
import * as esbuild from "https://deno.land/x/esbuild@v0.19.2/mod.js";
import { denoPlugins } from "https://deno.land/x/esbuild_deno_loader@0.8.2/mod.ts";

export type EsbuildBundlerOptions = {
  entryPoints: string[];
  outdir: string;
};

export const esbuildBundler = async (options: EsbuildBundlerOptions) => {
  const bundle = await esbuild.build({
    plugins: [...denoPlugins()],
    entryPoints: options.entryPoints,
    outdir: options.outdir,
    bundle: true,
    format: "esm",
    write: false,
  });
  esbuild.stop();

  return createMiddleware(async (c, next) => {
    const url = new URL(c.req.url);
    const output = bundle.outputFiles.find((v) => v.path == url.pathname);
    if (!output) {
      await next();
      return;
    }

    c.res = c.body(output.text);
  });
};

おわりに

HonoやDenoなどモダンな技術スタックを使った社内ツールについて紹介しました。 新しいフレームワークや言語をいきなりプロダクションで使うのは、安定性や社員の教育の面から難しい面も多々ありますが、社内ツールをはじめとした限定的なシーンからじわじわと使っていきたいものです。

「ラクスル課題解決型インターン」データサイエンスチームとして参戦!

はじめに

この夏「ラクスル 課題解決型インターン」に参加しました、インターン生の澤木です。僕はノバセルのデータサイエンスチームとして参戦したので、この記事ではその感想や、学びをまとめていこうと思います!

読んで欲しい人

  • ラクスルに興味がある人
  • 志望企業はまだ決まってないが夏インターンを成長機会にしたい人
  • DS/ML職でインターンや就職を考えている人

インターンシップ概要

  • 参加コース:ラクスル課題解決型インターン
  • 配属:ノバセル
  • チームの職種:データサイエンス・機械学習チーム
  • 参加日程:9/11 - 9/15
  • 報酬:10万円

2023夏のラクスルのインターンシップは「ハッカソン型」と「課題解決型」の2種類があり、僕は後者で参加しました。課題解決型インターンは、「ラクスルの開発組織が実際に直面している課題やタスクにアサインされ、エンジニアと共に業務を進める(募集ページより引用)」インターンシップです。

タスクごとにチームが分けられ、フロントエンドチームやサーバーサイドチームがラクスル事業部とノバセル事業部で1チームずつ、そして僕らが属するデータサイエンスチームがノバセル事業部で1チーム、合計5チーム編成でした。各チームは3〜4人( + メンター社員1人)で編成されており、課題解決型インターン全体の参加者は20人弱でした。

応募・選考について

このインターンとの出会いは、サポーターズが主催する逆求人イベントでした。そこで人事の方と面談をするまではラクスルは企業名は知っている程度で、そもそもエンジニア募集やインターンがあることすら知りませんでしたが、事業内容・インターン概要(そして報酬)どれにも魅力を感じオファーを受けることにしました。

選考に進む前に、後のメンターとなるデータサイエンティストの松村さんとの面談も設けていただき、ノバセルにおけるDS職の解像度が高まったことも参加への大きな動機になりました。

選考は技術課題と面接が1度ずつありましたが、専門性や高い技術力を求めているというよりかは、最低限のスキルチェックと、カルチャーフィットを見ているという印象でした。特に技術課題は、コーディングテストとしてはかなり丁寧な作りで、自分で手を動かしてデータの分析やモデリングをしたことのある学生なら十分に解ききれる実用的な良問でした。

データサイエンスチームの開発について

取り組んだ課題

実際にノバセル内部のお仕事にチャレンジさせていただいたため、その多くがNDA的に技術ブログに書くことができません…。ですが、伝えられる限りの情報で5日間の取り組みについて説明できたらと思っています。

それすなわち、僕らデータサイエンスチームの取り組みは、

「テレビCMの分析に必要な情報を画像から半自動でとって来れるようにしよう」

ということでした。ノバセルは広告代理店などからデータを受託し、効率の良いテレビCMの放映プランニングや効果分析を行なっています。そのためには分析できる形式のデータが必要不可欠なわけですが、テレビ業界のDX化はまだまだ道半ばで、綺麗なデジタル形式のデータが手に入らないことも多いようです。現状は手作業でデータ化やダブルチェックを行なっており、その現状を画像処理やデータサイエンス技術を使って脱しようというのが今回のインターンシップで取り組む課題でした。

補足:「ノバセル」について https://novasell.com/

分析・開発スケジュール

5日間(最終日は資料作成や発表のため実質4日間)という限られた時間で、どのように取り組むべきか、チームでまず作戦会議を行いました。しかし作業を進めていく中での軌道修正も多く、結論から言うと、5日間の開発は以下のような形で進みました。

【1日目@オンライン】

  • データの基礎分析・手法選定
  • 開発の役割分担

【2日目@オンライン】

  • 1日目で決めた主法の実装・課題の洗い出し
  • Bizの方々とのミーティング①
    • 現状の課題の聞き込み
    • 機能要件のすり合わせ

【3日目@オフィス】

  • それぞれの役割の実装を完了
  • 開発の繋ぎこみ

【4日目@オフィス】

  • streamlitを使った簡易的なアプリ開発
  • Bizの方々とのミーティング②
    • ここまでの開発の進捗共有
  • デザイナーの方からのFB会
    • アプリデザイン、ユーザビリティについてのフィードバック

【5日目@オフィス】

  • バグのFix
  • 発表資料作り
  • 発表会

初めは「機能だけ完成できればそれで良いのでは?」と考えていたチームメンバーでしたが、実際に実装をしてみると、精度100%の完璧なオートメーションは現実的でないことがわかってきました。さらに、ノバセルのBizの方々とお話をして、僕らは大切な視点を欠いていることに気付かされました。

Bizの方々と話した気づき
  • データサイエンス技術は”銀の弾丸”にはならない
    • 「絶対に精度100%」のモデルは実現できない
    • 仮に精度99%なら、その1%の修正ために結局人の手が介入しなくてはならない
  • データサイエンスの技術の「オペレーションとの融合」という視点
    • 業務フローの効率化を目指して技術を導入する
    • 完全な自動化を目的に掲げる必要はない
  • 機能や方法論だけでは実用には不十分
    • オペレーションに組み込むためには、「使える」ようにしなくてはならない
    • 半自動化した部分が誤りを生じうるなら、それを簡単に確認・修正できる必要がある

手元のデータセットで研究や解析をしたり、Kaggleなどのコンペで精度勝負ばかり取り組んでいる学生が見落としがちな視点だと思います。便利な学習モデルやツールが次々現れようと、ビジネス場面ではあくまで「道具としての技術」の1つであり、それを扱うエンジニアは常に業務フローの中でそれをどう組み込んで活かすかを考えなくてはなりません。機能だけではダメだと気づき、開発スケジュールを組み直して、オペレーションに載せられるようなWebアプリの作成まで視野に入れることにしました。

開発の進め方

1~3日目:画像処理・画像認識の実装

データサイエンスや機械学習周りの開発は、基礎分析と手法選定ができれば、比較的役割分担がしやすい領域だと思います。実際に、1日目の時点で大雑把な技術選定の目処が立ったため、与えられたタスクを実現するためのコーディングを大きく3つの工程に分け、三人で分担して取り組みました。それぞれの工程で各人が実験や実装を行い、何か進捗や疑問があればSlack・Github・オフラインで逐次共有を行い、議論しながら進めました。

4日目:Webアプリ実装

滑り出し好調であった開発ですが、途中でアプリまで作り切ろうと決まったことで、その開発を行った4日目はうまく役割分担ができず、急に開発が混乱しました。 実装が容易なstreamlitを使って開発を行いましたが、いかんせん3人とも初めましての技術だったので、全員が手探りで進めました。 この上なく贅沢なことに、ラクスル全体のデザイン統括をしている社員さんからユーザビリティやデザインのフィードバックをいただき、試行錯誤しながらなんとか動くものを完成させることができました。

5日目:スライド作成・発表準備

ほとんどが完成しました。細かなバグのFixを分担しつつ、発表資料を急いで作り、発表練習を行いました。発表時間は3分と非常に短かったため、背景から開発概要・技術詳細まで組み込むことに苦労しました。 結果としてはオーディエンス投票で1位となり、User Awardを取ることができたので、5日間の頑張りが報われてとても嬉しかったです。

開発以外のイベント

5日間のインターンはひたすら開発をしていたわけではなく、オフィスツアーやランチ、懇親会など様々なイベントが用意されていました。どれも楽しかったので、それぞれの学びや気づきをまとめます。

オフィスツアー

初出社日だった3日目の朝にありました。 何より驚いたのはオフィスのおしゃれさです。オフィスといえば整然とデスクが立ち並ぶ無彩色なイメージがありますが、ラクスルのオフィスは日経ニューオフィス賞を受賞しているだけあって、開放感があり、緑が多く、とても気分が晴れるデザインでした。エンジニアの出社は週1以上ですが、このオフィスならむしろ毎日出社したいと思える雰囲気でした。

ラクスルHPより引用

ランチ・懇親会

3日目の終業後の懇親会や、4日目のランチでは、インターン生だけでなく新卒社員の方や人事の方、他のグループのメンターの方などと広く交流することができました。畏まった場では聞きづらい採用や待遇についての話や、ラクスルの魅力など、お酒を飲みながらたくさん聞くことができたので、ただインターンに参加する以上に、カルチャーやビジョンの解像度が上がりました。僕らのチームメンバーは全員お調子者だったので、失礼なことをたくさん聞いてしまったような気がしますが、それでもNG無しと言わんばかりに答えていただけました。結果として、昔からの友達のように社員さんと仲良くなっているインターン生もいました笑

まとめ

全体として、ラクスルそしてノバセルの雰囲気を知りながら、楽しく課題に取り組める非常に有意義なインターンシップでした。メンターや社員さんのサポートも手厚く、懇親会を通じてNGなしで色々な話を聞くことができたので、自分自身非常に解像度が高まり良かったなと感じています。エンジニアインターン、特にデータサイエンス領域のものは、インターン用に作られた模擬的なタスクが与えられることもしばしばですが、ラクスルの課題解決インターンはインターン生を新入社員同等に扱い社内のリアルなタスクに取り組ませてもらえる点で非常に学びのあるものだったと思います。Tech側だけでなく、Biz側の視点も持ちながら取り組むことのできるリアリティに富んだインターンであるという点で、とても濃い5日間でした。僕らのチームはとにかく仲がよく、それも相まって笑いの絶えない5日間でもありました。改めて、最高のインターンシップをありがとうございました! この記事を読んでラクスルのインターンに少しでも興味を持つ方が増えれば嬉しいです。