GitLabのパッケージレジストリにnpmパッケージを公開する

以前、Tailwind CSSを使ったコンポーネントの設定を複数のプロジェクトで使いまわす方法を調べた。

GitリポジトリとしてGitLab.comを使っており、GitLabのパッケージレジストリにnpmパッケージを公開することで、公開範囲を絞ることができるため、その方法をメモ。

環境

Node.js v16.15.1, npm v8.11.0, Yarn v1.22.19。

OSはWindows 10 Pro 64bit 21H2、コマンドはGit 2.35.1.windows.2のGit Bashから実行して確認。

GitLabの設定

docs.gitlab.com

GitLab上でプライベートなプロジェクトを作成しておく。

パッケージレジストリの種類として、プロジェクトレベルとインスタンスレベルがある。今回はインスタンスレベルで設定する。

インスタンスレベルの場合、npmパッケージはスコープ付きにする必要がある。Gitプロジェクトが属するグループ名の先頭に @ を付与したものが、npmパッケージのスコープ名となる。

なお、実際のソースコードが書かれたGitプロジェクトと、npmパッケージを公開するGitプロジェクトは同じである必要はない。

パッケージ公開するNode.jsプロジェクトの設定

アクセストークンの発行

GitLab CI/CDによるパイプライン内ではなく、手動で実行する場合、パッケージレジストリとするGitプロジェクトにアクセス可能な、アクセストークンを発行する。アクセストークンのスコープは、 api のみでいい。

発行したトークンは、 .bashrc などで export NPM_TOKEN=... しておく。

package.json の変更

package.jsonname を、 <スコープ名>/任意のパッケージ名 に変更する。

GitLabのグループ名が my-org 、パッケージ名を components とする場合、 @my-org/components*1

また、 privatetrue であれば false にしておく。

.npmrc の変更

.npmrc に、以下の記述を追加。公開するGitLabプロジェクトのプロジェクトIDが必要となる。

<スコープ名>:registry=https://<GitLabドメイン>/api/v4/projects/<プロジェクトID>/packages/npm/
//<GitLabドメイン>/api/v4/projects/<プロジェクトID>/packages/npm/:_authToken=${NPM_TOKEN}

${NPM_TOKEN} には環境変数が設定される。GitLabドメインは、GitLab.comを使用している場合は gitlab.com 、オンプレミス環境であればそのドメインを設定する。

例として、GitLab.com上のGitプロジェクトで、グループ名が my-org , プロジェクトIDが 12345678 の場合、以下のようになる。

@my-org:registry=https://gitlab.com/api/v4/projects/12345678/packages/npm/
//gitlab.com/api/v4/projects/12345678/packages/npm/:_authToken=${NPM_TOKEN}
GitLab CI/CDのパイプライン経由で公開する場合

GitLab CI/CDのパイプライン経由でパッケージ公開を行う場合、CI/CD時の環境変数を利用するよう書き換えることも可能

@my-org:registry=https://gitlab.com/api/v4/projects/12345678/packages/npm/
//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}

レジストリURLは、 @${CI_PROJECT_NAMESPACE}:registry=https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/ のようにも書き換えられそうだが、ローカルでうっかり npm publish したときに www.npmjs.com に向きそうなので、固定値で設定している。

公開処理の実行

npm publish --dry-run し、 Publishing to ... で表示されるURLが、指定したレジストリURL(この例では https://gitlab.com/api/v4/projects/12345678/packages/npm/ )となっていれば正しく設定できている。

npm publish を実行したら、ブラウザで公開先のGitLabプロジェクトのページを開き、左フレームの「パッケージとレジストリ」>「パッケージレジストリ」を開く。

指定したパッケージ名およびバージョンで、npmパッケージが登録されていれば成功。

パッケージを利用するNode.jsプロジェクトの設定

アクセストークンの発行

パッケージレジストリとするGitプロジェクトにアクセス可能な、アクセストークンを発行する。アクセストークンのスコープは、 read_api のみでいい。

また、スコープ apiread_api を含むので、パッケージ公開するNode.jsプロジェクトの設定を行っているのであれば流用可能。

.npmrc の変更

.npmrc に、以下の記述を追加。公開側との違いとして、 /project/<プロジェクトID> が不要となっている。

<スコープ名>:registry=https://<GitLabドメイン>/api/v4/packages/npm/
//gitlab.com/api/v4/packages/npm/:_authToken=${NPM_TOKEN}

例として、GitLab.com上のGitプロジェクトで、グループ名が my-org の場合、以下のようになる。

@my-org:registry=https://gitlab.com/api/v4/packages/npm/
//gitlab.com/api/v4/packages/npm/:_authToken=${NPM_TOKEN}

この設定が正しく行われていれば、普通に npm install できる。

Yarnを用いる場合

それぞれ npm publish および npm install で動作確認したが、プロジェクトのパッケージマネージャーとしてはYarn Classicを使っている。

それぞれ yarn publishyarn add に置き換えられないか確認した。

yarn publishによるパッケージ公開

結論から言うと、まったく使い物にならなかったので、 npm publish を使い続けることとした。

yarn publish はデフォルトでは対話型だが、 yarn publish -non-interactive で非対話型で実行できるものの、いろいろと問題があった。

  • パッケージに含めるファイルを調整したときに、確認のため npm pack --dry-run を使うが、 npm pack に該当するコマンドがYarnにない
  • package.jsonfiles で指定していないファイルがパッケージに含まれる

yarn publishの改善のissueもあるが、改善されそうもないので、 npm publish を使う。

yarn addによるパッケージ追加

yarn add を実行すると以下のエラーが発生する。

error An unexpected error occurred: "https://ドメイン>/api/v4/projects/<プロジェクトID>/packages/npm/<スコープ名>/<パッケージ名>/-/<スコープ名>/<パッケージ名>-<バージョン>.tgz: Request failed \"404 Not Found\"".

同じ問題の質問が、GitLabフォーラムにあった。

forum.gitlab.com

コメントにあるように、 .npmrc//<GitLabドメイン>/api/v4/projects/:_authToken=${NPM_TOKEN} を追加すると成功するようになった。

先の例に追加すると、以下のようになる。

@my-org:registry=https://gitlab.com/api/v4/packages/npm/
//gitlab.com/api/v4/packages/npm/:_authToken=${NPM_TOKEN}
//gitlab.com/api/v4/projects/:_authToken=${NPM_TOKEN}

振り返り

基本的には公式ドキュメント通りに進めるだけだったが、Yarnを使う場合にはもろもろ手直しが必要だった。

特にpublishの挙動の違いにはまいった、なぜうまく動かないのかもよくわからない。npm用の設定ファイルが読み込まれていないのか?

ただ、掘り下げるのも時間の無駄なので、調査はしていない。

あと、そろそろYarnもmodernに乗り換えないとかなぁ。

*1:グループ名は example にしようと思ったが、GitLab.com上に存在していた

Visual Studio Codeで開いているファイルをエクスプローラー上でフォーカスしないようにする

VSCodeのデフォルト設定だと、開いているアクティブなファイルが左フレームのエクスプローラー上でフォーカスされる。

この時、ファイルの保存されているフォルダが展開されるので、気づくとあちこちのフォルダが開かれていたりする。また、開いているファイルを切り替えるたび、エクスプローラーが動くので気になることも多い。

普段はこのフォーカスをOFFにしつつ、必要な時にはキーボードショートカットで実行する方法を調べたのでメモ。

環境

Visual Studio Code v1.74.3

開いているファイルへのフォーカスを抑制

Stack overflowに同様の質問あり。

stackoverflow.com

VSCodeの設定から、 explorer.autoRevealfalse にすると、開いているファイルのフォーカスおよびフォルダの展開を抑制できる。

特定のフォルダのみフォルダを展開する

Visual Studio Code v1.74より、 explorer.autoRevealtrue の場合、フォルダごとに展開の有無を指定できるようになった。

code.visualstudio.com

VSCodeの設定から、 explorer.autoRevealExclude に、Globでパスを指定できる。

デフォルトでは以下のように、 node_modules などが除外されている。

{
  "explorer.autoRevealExclude": {
    "**/node_modules": true,
    "**/bower_components": true
  }
}

キーボードショートカットで開いているファイルをエクスプローラー上でフォーカスする

これで自動的なフォーカスは抑制できたので、今度は任意でフォーカスできるよう設定する。

これまたStack overflowに同様の質問あり。

stackoverflow.com

キーボードショートカットの「File: Reveal Active File in Explorer View」が対象。

ファイル > ユーザー設定 > キーボード ショートカットにて、 Reveal Active で検索すると出てくる。

デフォルトでは、キーバインド未設定のため、 Ctrl + Alt + oバインディングした。

キーバインド設定後

振り返り

2年前くらいからこの設定を行っていたが、職場でどうやっているのか聞かれたのと、v1.74でディレクトリ単位で展開の制御ができるようになったので、合わせて書いてみた。

こちらのissueで記載の通り、昔は node_modules もフォルダ展開されていたが、今は explorer.autoRevealExclude で抑制されるので、デフォルトでも不便はないかも。

ただ、キーボードショートカットでのフォーカスがすっかり手になじんでしまったので、もう戻すのは無理だなぁ。

Storybookのアドオンパネルが表示されない場合の対応

Storybookを使っていると、アドオンパネルが開かなくなった。

Controlsでプロパティの変更ができず、ハマったので対応方法をメモ。

環境

Storybook 6.5.13 for React。

問題

StorybookをGoogle Chromeで開き、 Show addons 状態にしても、アドオンパネルが表示されない。

一度発生すると、ヘッダーアイコンのクリック、キーボードショートカット A 、どちらを使っても解消しないが、Firefoxなどの別ブラウザで開くと、アドオンパネルが表示される。

対応

Knobsのアドオンパネルが開かないというissueがあり、その中のコメントに対応方法が記載されていた。

github.com

Sometimes the UI gets into a funny state where the addon panel is hidden based on local storage. In that case, running localStorage.clear() in the browser console and hard-reloading the page should fix it.

原因は同じようなものだろうと思ったので、記載の通り、ブラウザの開発者ツールを開き、コンソールタブから localStorage.clear() を実行。

ページを再読み込みすると、無事に表示された。

UIの状態管理をローカルストレージで行っているようで、おかしくなったらクリアすればいい模様。

別ブラウザで開くと解消したのも、ローカルストレージの状態によるものと思われる。未確認だが、シークレットウィンドウで開いても解消するかもしれない。

振り返り

スプリントレビューでコンポーネント確認を行う直前に発生したのでえらく焦った。

参考にしたissueは #17714 を含む6.5.0-alphaのリリースでcloseされていたが、どうもこのPRはアドオンパネルの開閉ボタンの追加で、ローカルストレージの不整合の対応ではなさそう。

とりあえず、Storybookでトラブルが発生したら、 localStorage.clear() してみることとしよう。

tailwind.config.jsの設定を共有し、複数プロジェクトで使う

Tailwind CSS + daisyUIで作成したコンポーネントを、複数のプロジェクトで共有しようとしたが、単純にコンポーネントのソースだけを読み込んでも、 tailwind.config.js の設定内容が引き継がれない。

設定を手動でコピーするのも現実的ではないため、共有する方法を調べたのでメモ。

環境

Tailwind CSS v3.2.4、daisyUI v2.43.0。

共有方法

読み込むプロジェクトでTailwind CSSを使っているかによって、方法が異なる。

Tailwind CSSを使っている場合は、プリセット機能が使える。

Tailwind CSSを使っていない場合は、CSSファイルとして設定内容を出力する。

読み込むプロジェクトでTailwind CSSを使っている場合

Tailwind CSSのPresetsを用いる。

tailwindcss.com

tailwind.config.jspresets として、配列で別の tailwind.config.js を読み込むと、プリセットをベースとして、 tailwind.config.js で記述した内容がマージされる。

module.exports = {
  presets: [
    require('@example/components/tailwind.config.js')
  ],
  ...
 }

ファイルパスではなく require で読み込むため、npmパッケージとしてコンポーネントを公開する場合、ファイルを参照できるようにしておく必要がある。

プラグイン設定も引き継げるので、 daisyUI の設定もそのまま利用可能。

コンポーネントをnpmパッケージとして公開する場合は、Tailwind CSSやdaisyUIは、 devDependencies ではなく dependencies としてインストールしておくといい。

引き継がれない設定

presets で読み込んだファイルに記述されていても、無視される設定がある。 Merging logic in-depth参照。

例えば、CSSクラス名のスキャン対象となるファイルを指定する content は、プリセットとして読み込んだ設定が引き継がれない。

npmパッケージやモノレポとして共有する場合、 content で読み込んだコンポーネントパッケージ側のソースファイルを指定しないと、コンポーネントパッケージ内でのみ使用されたクラスが有効にならないことに注意。

module.exports = {
  presets: [
    require('@example/components/tailwind.config.js')
  ],
  content: [
    './src/**/*.{js,ts,jsx,tsx}',
    // コンポーネント内のファイルも対象に含める
    './node_modules/@example/components/src/**/*.{js,ts,jsx,tsx}',
  ]
  ...
 }

読み込むプロジェクトでTailwind CSSを使っていない場合

@tailwind base; などを記述しているCSSファイルを入力として、 tailwindcss -i 入力CSSファイル -o 出力CSSファイル で、Tailwind CSSプラグインのクラスをCSSファイルとして出力できる。-m オプションでminifyも可能。

tailwindcss.com

npx tailwindcss -m -i src/styles/style.css -o tailwind.style.min.css

出力したCSSファイルを、コンポーネントを使うプロジェクトで読み込んでやればいい。

振り返り

前回でも少し書いたが、プリセットが便利。

コンポーネント側のソースコードcontent に含めるといった注意点はあるものの、 primary, secondary のような色をコンポーネント側で宣言しておき、必要に応じて上書きすることで、色をプロジェクトごとに変更、といったことが簡単にできた。

Tailwind CSSで色やブレークポイント、クラスを追加する

Tailwind CSSで、レスポンシブ用のブレイクポイントの追加や、色などのクラス名を追加する方法を調べたのでメモ。

環境

Tailwind CSS v3.2.4。

ブレイクポイントの追加

以下のリンクにデフォルトのブレイクポイントが記載されているが、最小で幅640pxになっているので、もっと小さい幅を指定したい。

tailwindcss.com

カスタマイズの方法はこちらに記載あり。

tailwindcss.com

tailwind.config.js を変更すればいいが、既存のブレイクポイントより大きいものを追加する場合と、小さいものを追加する場合で、設定方法が異なる。

大きいものを追加する場合、 theme.extends.screens を設定すればいい。

module.exports = {
  theme: {
    extends: {
      // デフォルトのブレイクポイントに、3xlが追加される
      screens: { '3xl': '1600px' },
    },
  },
}

小さいものを追加する場合、 theme.extends.screens に追加しても、追加したブレイクポイントがCSSとして出力されたときの宣言順が末尾になるのか、有効にならない。

theme.screens を設定するとデフォルトの上書きになるため、追加するブレイクポイントの後にデフォルトの値を展開するという、ややトリッキーな設定を行うことで、小さいブレイクポイントを追加できる。

module.exports = {
  theme: {
    // extends 不要
    screens: {
      xs: '420px',
      // デフォルトのブレイクポイントを展開
      ...require('tailwindcss/defaultTheme').screens,
    },
  },
}

色の追加

tailwindcss.com

CSSファイルでの設定方法などもあるが割愛。 tailwind.config.jstheme.colors で上書き、 theme.extends.colors で追加ができるので、今回は後者を使う。

色名: 'カラーコード' で単一の値、 色名: { キー: 'カラーコード'... } でネストしたオブジェクトのキーごとに値を設定できる。

ドキュメントに記載はないが、多重ネストも可能。ただ、v3.1.4あたりでは動かなかった気がするので、使わないほうが無難か。

設定した色名は、 bg-<色名>text-<色名> のように使う。オブジェクトをネストした場合、 bg-<色名>-<ネストしたオブジェクトのキー> のように使用できる。また、オブジェクトのキーを DEFAULT にした場合、色名のみで使用可能となる。

module.exports = {
  theme: {
    extends: {
      colors: {
        disabled: '#ccc',
        primary: {
          DEFAULT: '#2563eb', // primary
          light: '#3b82f6', // primary-light
          dark: '#1d4ed8', // primary-dark

          // 多重ネストも一応動くが、
          // キーを 'primary-sub' にしても同じなので、
          // そっちを使ったほうが無難だと思う
          sub: {
            DEFAULT: '#bfdbfe', // primary-sub
            light: '#dbeafe', // primary-sub-light
            dark: '#93c5fd', // primary-sub-dark
          }
        },
      },
    },  
  },
}

<div className="bg-primary">...</div>
<div className="bg-disabled text-primary-light">...</div>

色指定時の注意点

デフォルトで宣言されている色名や、プリセット機能などで既存の色を拡張する場合、オブジェクトをネストした形式であれば元の設定とマージされるが、文字列形式だと上書きされる模様。

module.exports = {
  theme: {
    extends: {
      colors: {
        gray: '#ccc', // bg-gray-100 などが使えなくなる
        red: { DEFAULT: '#f00' }, // bg-red-100 なども有効
      },
    },  
  },
}

設定時にはオブジェクト形式を使っておくのが無難か。

その他のクラスの追加

色と同じ要領で、 theme.extends の下にオブジェクトで指定できる。

module.exports = {
  theme: {
    extends: {
      fontSize: { '2xs': '0.75rem' },
      width: { 15: '3.75rem' },
      height: { 15: '3.75rem' },
      boxShadow: {
        top: '0 -4px 2px rgba(0, 0, 0, 0.1)',
      },
    },  
  },
}

振り返り

実際の運用では、ベースとなる tailwind.config.js を複数プロジェクトからプリセットで使いまわしているので、色として primarysecondary を宣言しておくと、各プロジェクトでのカスタマイズがしやすいので便利。

また、デフォルトだと色が多いので、実際の運用ではUsing the default colorsAliasing color namesの方法で、色名を絞ると使いやすい。

const colors = require('tailwindcss/colors')

module.exports = {
  theme: {
    colors: {
      transparent: colors.transparent,
      current: colors.current,
      black: colors.black,
      white: colors.white,
      gray: colors.gray,
      red: colors.red,
      green: colors.green,
      blue: colors.blue,
    },
  },
}

daisyUIのtableクラスで、ヘッダーが大文字になったり固定されるのを抑制する

daisyUIの table クラスを使うと、theadのth,tdタグに書いたアルファベットが大文字に変換される。

また、スクロール可能なテーブルの場合、最初のthタグの位置が固定されてしまう。

ちょっとイマイチな挙動のため、変更する方法を調べたのでメモ。

環境

Tailwind CSS v3.2.4, daisyUI v2.43.0。

theadの大文字変換

公式のコンポーネント紹介を見てもらうとわかるが、thead配下のthタグのテキストが大文字に変換される。

text-transform: uppercase; 、Tailwind CSSだと uppercase クラスが付与されている模様。

以下がソースコードの対象部分。theadだけでなく、tfootでも同様の変換が行われる。

github.com

  :where(thead, tfoot) {
    :where(th, td) {
      @apply bg-base-200 text-xs font-bold uppercase;
    }
  }

table クラスにModifierがないか確認したが、v2.43.0時点では用意されていない。

daisyUIの設定で変更できるか調べたが、テーマの追加はできるが既存のクラスの変更はできない模様。

<thead className="!normal-case"><tr className="!normal-case"> で上書きできるかと思ったが、カスケード層が異なるからか、効果がない。

thタグに <th className="normal-case"> すれば有効となるが、都度記述するのが面倒。theadで <thead className="[&_th]:normal-case"> でも変換を無効化できるが、そもそも変換させたくない。

Next.jsを使っているので、結局 styles/global.css に以下を追加して対応した。 元の定義の :where の階層と同様だが、上書きされることは想定しないので、 :is を使用。

.table :is(thead, tfoot) :is(th, td) {
  text-transform: none;
}

ヘッダーの最初のthが固定される

以下のissueのgifを見てもらうとわかりやすいが、スクロールしたときに、theadの最初のthが固定され、スクロールしない。

github.com

これまたtheadの大文字変換同様、v2.43.0時点ではModifierがないので、 styles/global.css に以下を追加して対応。

.table th:first-child {
  position: relative;
}

振り返り

最終的に、両方CSSで上書きすることになった。デフォルトでこれはちょっとおせっかいな気がする。

特にヘッダーのセル固定、行全体を固定ならともかく、なんで最初のセルだけ固定しているんだろう?

issueのコメントでも「デフォルトでstickyにするのはやめて table-sticky-header オプションを追加しない?」と書かれているが、ヘッダーの最初のセルを固定するというのはどこから来た挙動なんだろうか。

Notionのページをエクスポートするときにプロパティを除外する

Notionをissueトラッカーとして使い、調査や設計した内容を書き溜めている。

先日、調査結果を社外のステークホルダーにも共有することとなった。

Notionは外部ユーザーと共有できるが、社内ルールで外部との共有は禁止されており、やるなら稟議が必要。かといって、Notionの記載内容をパワポ等に移すのも面倒。

Notionのエクスポート機能で、ページをPDFにして渡せばいいかと思ったが、試してみるとページの先頭のプロパティまでエクスポートされてしまう。プロパティをエクスポート対象から除外するか、非表示にする方法がないか調査したのでメモ。

環境

Notionはアプリ版ではなく、Webブラウザ版を利用。

問題

www.notion.so

Notionのページをエクスポートすると、プロパティも出力されてしまう。

エクスポートする際に、画像やファイルを除外することはできるが、プロパティの除外はできない模様。

プロパティには社内で利用するための情報(分類、期限など)も含まれているため、社外に共有する資料には含めたくない。

調査

notion export exclude properties で検索したら、RedditのNotionサブレに同じ質問があった。

www.reddit.com

以下のコメントに、対応方法が記載されている。サブページであればプロパティが表示されないので、サブページを作ってそこからエクスポートすればいい。

jedybg comments on Removing Properties for export

This is a hack, but works to avoid having the issue in the meantime:

  1. Create a subpage on top of the page you'll be exporting.
  2. Move all the content from your page to your subpage.
  3. CTRL/CMD + Click the subpage to open it in a new tab (this will be important in a bit).
  4. Export the subpage & close its' tab.
  5. Use CTRL/CMD + Z to undo the subpage creation.

Not the perfect solution obviously, but extremely useful since we can't do this with a menu button.

イメージ通りの挙動になるので、この方法を採用。

対応

エクスポートする内容を記述していない場合

www.notion.so

ページ内で /page をタイプし、「このページ内に子ページを埋め込みます。」でサブページを作成できる。あとは作ったページに内容を書いていけばいい。

エクスポートする内容を記述済みの場合

あらかじめ、親となるページを作成しておく。

記述済みのページを開いてから、メニューから「別ページへ移動」をクリックするか、キーボードショートカット「 cmd/ctrl + shift + P 」で、移動先のページ選択ダイアログが開く。

移動先のページを選択すると、そのページのサブページとなる。

振り返り

今回は、もともと記述したページがあったので、そのページを複製し、複製元のページのサブページにしてエクスポートした。

Notionに記載した見た目のままでPDFになるので大変便利。

社外への共有というイレギュラーな対応だったが、結構使えそう。