n_hachiのメモ

メモです。

express使用時のapp.use(express.static(__dirname + '/public'))の意味

背景

他者がつくったアプリを拝見していたときに app.use(express.static(__dirname + '/public')) と書かれた箇所があり、この文の意味が分からなかったので調べた。

説明

Express での静的ファイルの提供に説明があった。

イメージ、CSS ファイル、JavaScript ファイルなどの静的ファイルを提供するには、Express に標準実装されている express.static ミドルウェア関数を使用します。
静的アセットファイルを格納しているディレクトリーの名前を express.static ミドルウェア関数に渡して、ファイルの直接提供を開始します。例えば、public というディレクトリー内のイメージ、CSS ファイル、JavaScript ファイルを提供するには、次のコードを使用します。
(中略)
ただし、express.static 関数に指定するパスは、node プロセスを起動するディレクトリーに対して相対的です。別のディレクトリーから Express アプリケーションを実行する場合は、提供するディレクトリーの絶対パスを使用する方が安全です。

試してみる

app.use(express.static('public'))app.se(express.static(__dirname+'/public'))の違いを確認するリポジトリを作成した。
n-hachi/prac_express_app_use

以下に確認方法を記す

前準備

アプリのインストールおよび起動準備

git clone https://github.com/n-hachi/prac_express_app_use
cd prac_express_app_use
npm install

case1:リポジトリのトップディレクトリでnodeを実行する

# case1
node index.js

この状態なら http://localhost:8000/path1http://localhost:8000/path2 ともにアクセスできる。

case2:リポジトリのトップ以外のディレクトリでnodeを実行する

まずcase1で走らせたnodeプロセスを終了する

mkdir tmp
cd tmp
node ../index.js

case1とは異なり http://localhost:8000/path1 はアクセスできるが http://localhost:8000/path2Cannot GET /path2/と表示される。
これはファイルtmp/path2/index.htmlが存在しないために発生する。

まとめ

app.use(express.static(__dirname + '/public'))の意味を勉強した。
あと__dirnameをつけないときはapp.use(express.static('public'))のようにpublicの前にスラッシュが不要なのだが、__dirnameを付加するときはpublicの前にスラッシュが必要なことに気づかず、数分なやんでしまった。
もし同じような動きになったときはスラッシュの有無を確認してほしい。

autocmdの設定をvim実行時に削除する方法

背景

自分は.vimrcに以下一行を追加することでファイル保存時に各行末尾に存在する空白を自動で削除するようにしている。

" Delete tailing blanks.
autocmd BufWritePre * :%s/\s\+$//ge

この方法は他者が作成したコードの末尾に空白が存在する場合、意図していない箇所が書き換わってしまいgit diffを実行した場合に関係ないところまで表示されてしまい困るという課題がある。
上記理由からautocmdの設定を削除する方法を調べた。

参考箇所

autocmd - Vim日本語ドキュメントの "3. 自動コマンドの除去" に以下のように書かれている

:au[tocmd]! [group] {event} {pat} {event} と {pat} に関連づけられた全ての自動コマンドを 除去する。

解決方法

vim起動時に以下を実行することで自動末尾空白が消されることがなくなった

:autocmd! BufWritePre *

もうちょっと良い方法があるかも?

node_modules内パッケージのエントリーポイント定義方法

背景

最近node.jsを勉強しており、他者が作成したパッケージに含まれるファイルの中でどれがエントリーポイントとなるのかわからなかったので調べた。

パッケージのエントリーポイント

Modules: Packages | Node.js v16.1.0 Documentationの一部を訳し以下に記す。
※訳に誤りがあれば指摘いただきたい。

パッケージ内のファイルpackage.jsonはエントリーポイントを定義する2つのフィールドE、"main""exports"を定義することができる。
"main"はどのバージョンにも対応しているがパッケージのメインエントリポイントのみ定義可能な制限を持つ。

"exports""main"の代替を提供する。 "exports"はパッケージのメインエントリポイントを定義するのと同時に、パッケージをカプセル化し、"exports "で定義されたもの以外のエントリーポイントを防ぐ。
※この部分は訳した結果、完全には理解できていないです。

まとめるとpackage.json内の"main"もしくは"exports"フィールドで指定するようだ。

具体例

ここで今回の調査のきっかけになったパッケージを見てみる。
grpc-node/packages/grpc-js at master · grpc/grpc-node
このリポジトリはgrpcをjavascriptで使うために必要なパッケージらしい。
ここvar grpc = require('@grpc/grpc-js');という一文が記載されている。
grpc-jsに含まれるpackage.jsonを見てみる。
ローカルの環境ではnode_modules/@grpc/grpc-js/package.jsonに当たる。
エントリーポイントに関連する部分は"main": "build/src/index.js",である(github上の対応する箇所)。
これによってnode_modules/@grpc/grpc-js/build/src/index.jsがエントリポイントと設定される。

まとめ

package.jsonに含められた"main""exports"フィールドによってエントリポイントは定義される。
"main""exports"の違いがわかってないので、今後必要になったタイミングで調べていきたい。

create-react-appのメモ

背景

最近 create-react-app を使ったアプリ作成を勉強している。
その過程で、どのファイルが最初に呼ばれるのか(エントリーポイントといえばよいのだろうか?)がわからなくなったので調べてみた。
このメモを作成したときに使ったnpxのバージョンは以下の通り。

$ npx -v
6.12.0

前準備

環境を構築する

$ npx create-react-app myapp
$ cd myapp

上記コマンドにより下記構成が作成される。(※内容が多すぎるためnode_modules は表示していない。)

$ tree -I node_modules
.
├── package.json
├── package-lock.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── README.md
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── reportWebVitals.js
    └── setupTests.js

2 directories, 17 files

この状態で npm start を実行するとブラウザが立ち上がりlocalhost上で動作するページが起動する。

npm startの概要

npm start に関連する内容は package.json に記載されている。
ここでは関連する部分のみ抜粋する。

{
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
}

※ここからは予想を含む。
上記"scripts"要素内の記載からキー:"start"に対応する値:react-scripts startが実行される。
react-scripts./node_modules/.bin/react-scriptsに配置された実行ファイルが使われる。

Folder Structure | Create React App に以下記述を見つけた。

For the project to build, these files must exist with exact filenames:
・public/index.html is the page template;
・src/index.js is the JavaScript entry point.

この内容から、public/index.htmlsrc/index.jsが必須ファイルであると予想がたつ。 またreact-scriptsはデフォルトのエントリポイントとしてpublic/index.htmlを扱っているのだろうと推測できる。

stackoverflowの記事

上記推測の裏付けを取るため調べたら関連しそうな記事を見つけた

以下に一番評価された回答の概要を示す(訳はかなり雑です、すいません)。

1. package.json内、start要素

package.json内のstart要素から探索を始めた。

"start": "react-script start"

2. react-scripts

上記内容からnode_modules/.bin以下に存在するreact-scriptsに到達した。 以下に関連する箇所を記載する。

switch (script) {
  case 'build':
  case 'eject':
  case 'start':
  case 'test': {
    const result = spawn.sync(
      'node',
      [require.resolve('../scripts/' + script)].concat(args),
      { stdio: 'inherit' }
    );

上記内容から../scripts/ディレクトリ以下のスクリプトを探した。

3. start.js

続いてnpm startに関連すると予想されるファイル、node_modules/react-scripts/scripts/start.jsを開いた。 このファイル内でwebpackの設定ファイルを見つけた。 これらはnode_modules/react-scripts/config/webpack.config.dev.jsに記載されていた。 以下に関連した箇所を抜粋する。

entry: [
  // Finally, this is your app's code:
  paths.appIndexJs,
],
plugins: [
  // Generates an `index.html` file with the <script> injected.
  new HtmlWebpackPlugin({
    inject: true,
    template: paths.appHtml,
  }),

よってwebpack configに含まれているpaths.appIndexJsで参照されるファイルがエントリファイル(エントリポイント?) である。

4. paths.js

関連する最後のファイルはpaths.js(おそらくnode_modules/react-scripts/config/paths.js)である。

const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
module.exports = {
  ...
  appHtml: resolveApp('public/index.html'),
  appIndexJs: resolveApp('src/index.js'),
  ...
}

5. appHtmlおよびappIndexJs

以上からアプリケーションディレクトリ内のappHtmlはファイルpublic/index.htmlであり、appIndexJsはファイルsrc/index.jsである。

まとめ

※公式ページの記載を見つけられなかったので、以下内容は調べた結果からの予想です。
npx create-react-appを使いアプリをつくる場合、public/index.htmlsrc/index.jsは自動的に読み込まれると予想する。

最後に誤りなどあれば指摘ください。

Express web framework (Node.js/JavaScript)の勉強 part4、 directory-structure

The generated projectを読み進める。
日本語版の資料をかいつまんだような内容なので、詳細を知りたい人はこっちを見ることを勧める。

ディレクトリ構造

expressおよび関連するコマンドを実行することで以下のようなディレクトリ構造になる。
注意:一部コマンドの出力結果は省略している。

$ express express-locallibrary-tutorial --view=pug --git

   create : express-locallibrary-tutorial/
   create : express-locallibrary-tutorial/public/
   create : express-locallibrary-tutorial/public/javascripts/
   create : express-locallibrary-tutorial/public/images/
   create : express-locallibrary-tutorial/public/stylesheets/
   create : express-locallibrary-tutorial/public/stylesheets/style.css
   create : express-locallibrary-tutorial/routes/
   create : express-locallibrary-tutorial/routes/index.js
   create : express-locallibrary-tutorial/routes/users.js
   create : express-locallibrary-tutorial/views/
   create : express-locallibrary-tutorial/views/error.pug
   create : express-locallibrary-tutorial/views/index.pug
   create : express-locallibrary-tutorial/views/layout.pug
   create : express-locallibrary-tutorial/.gitignore
   create : express-locallibrary-tutorial/app.js
   create : express-locallibrary-tutorial/package.json
   create : express-locallibrary-tutorial/bin/
   create : express-locallibrary-tutorial/bin/www

   change directory:
     $ cd express-locallibrary-tutorial

   install dependencies:
     $ npm install

   run the app:
     $ DEBUG=express-locallibrary-tutorial:* npm start

$ cd express-locallibrary-tutorial
$ npm install

up to date, audited 123 packages in 2s

6 packages are looking for funding
  run `npm fund` for details

3 low severity vulnerabilities

To address all issues, run:
  npm audit fix --force

Run `npm audit` for details.

$ tree -F
.
├── app.js
├── bin/
│   └── www*
├── node_modules/
│   ├── たくさんのサブディレクトリおよびファイル(表示は省略)
│   ︙
│
├── package.json
├── package-lock.json
├── public/
│   ├── images/
│   ├── javascripts/
│   └── stylesheets/
│       └── style.css
├── routes/
│   ├── index.js
│   └── users.js
└── views/
    ├── error.pug
    ├── index.pug
    └── layout.pug

405 directories, 4784 files

nodemon

今後express-locallibrary-tutorial以下を編集していくのだが、編集するたびアプリを起動し直すのは面倒である。
この煩わしさを解消するnodemonというツールがある
下記コマンドを実行し、nodemonをインストールする。

npm install -g nodemon

もし開発環境以下(?)にインストールしたい場合は-gの代わりに--save-devを使う。
nodemonを使ってアプリを起動すると、ファイル編集時に自動で反映してくれるためアプリの再起動の手間が省ける。

nodemon ./bin/www

package.json

package.jsonはアプリの依存情報を保持する
nodemonをインストールした直後のpackage.jsonの内容を以下に示す。
タイミングによってバージョン番号は変化するので、次同じコマンドを実行しても下記と同じ結果になるとは限らない(と思う)。

{
  "name": "express-locallibrary-tutorial",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www",
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "express": "~4.16.1",
    "http-errors": "~1.6.3",
    "morgan": "~1.9.1",
    "nodemon": "^2.0.7",
    "pug": "2.0.0-beta11"
  }
}

npm run-script

参考:npm-run-script | npm Docs
npm run-scriptコマンドによってpackage.json"scripts"オブジェクトに記載されたコマンドを実行できる。
上記の場合、"scripts"というオブジェクトの中にキー"start"、バリュー"node ./bin/www"が存在しているため、npm run-script startと入力するとnode ./bin/wwwが実行される。
引数なしの場合は、以下のように利用可能なコマンドが表示される。

$ npm run-script
Lifecycle scripts included in express-locallibrary-tutorial:
  start
    node ./bin/www

以下npm run-script -hの実行結果からわかるようにrun-scriptrunというエイリアスを持っている。
入力文字の短さからnpm runを使ったほうが良いだろう(npm run-scriptのほうがnpm runより良いケースってあるのだろうか?)。

$ npm run-script -h
npm run-script <command> [-- <args>]

aliases: run, rum, urn

scripts

package.json内のscriptsを以下のように編集する

"scripts": {
  "start": "node ./bin/www",
  "devstart": "nodemon ./bin/www",
  "serverstart": "DEBUG-express-lcallibrary-tutorial:* npm run devstart"
}

"start"で始まる行の末尾にカンマ(,)が追記されていることに注意する。
以降はnpm run serverstartと実行することでnodemonでアプリが実行される

/bin/www

ファイル/bin/wwwはアプリケーションのエントリポイントである。
下記コードによって、後述するapp.jsを呼び出す。

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');

残りのコードでapp.jsを使用するHTTPサーバの設定、ポートの設定、リスニングやサーバエラーのレポートを開始するらしい。

/app.js

expressアプリケーションオブジェクト(appという名前が慣習である)を生成、アプリケーションおよびミドルウェアの設定、appをエクスポートする。
上記した/bin/wwwはエクスポートされたappを使用する。

var express = require('express');
...
var app = express();
...
module.exports = app;

app.jsの詳細を見ていく。
最初にいくつかのnodeライブラリを取り込む。

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

その後require()を使い、routesディレクトリからモジュールを取り込む。
これらファイルには、特定のURLへのリクエストを受け取った場合の処理が書かれている。

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

次にappオブジェクトをexpressモジュールを使い作成し、view(template)エンジンを設定する。
エンジンの設定は2段階に分かれる。
最初に値'views'を使ってテンプレートが保存されているフォルダを指定する。
その後、値'view engine'を使いテンプレートライブラリを指定する。
参考:Using template engines with Express

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

続いてapp.use()を使いミドルウェアライブラリをリクエストハンドリングの連鎖に加える。

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

更に特定のパスへの要求を別のルータへ対応付ける。

app.use('/', indexRouter);
app.use('/users', usersRouter);

最後はエラーとHTTP404レスポンスを返却するためのミドルウェアを追加する。

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

以上でexpressアプリケーションオブジェクト(app)の設定は完了する。
最後にmodule.exportsappを追加することで、/bin/wwwからアクセスできるようにする。

module.exports = app;

Router

/routes以下にはusers.jsindex.jsがあるが基本的に処理が同じなので、ここではindex.jsのみを見ていく。
最初にexpressモジュールをロードしexpress.routerオブジェクトを取得する。

var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource!!');
});

module.exports = router;

Views(templates)

views(template)は/viewsディレクトリに格納され、拡張子.pugを持つ。
Response.render()メソッドは特定のテンプレートを描画するために使われ、レスポンスとして結果を送信する。
以下/routes/index.jsの例ではルートレンダーはテンプレートindex(index.pug)を使い、テンプレート変数titleを渡している。

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

上記コードに対応するテンプレート/views/index.pugを以下に示す。
ここでは数変titleの箇所に値Expressが挿入される。

extends layout

block content
  h1= title
  p Welcome to #{title}

まとめ

個人的にこの章は今までメインでやってきたこと(組み込み寄り)と比べて多くの差異を感じた。
特に重要に違いを感じた点を以下にまとめる。

  • npm expressでアプリケーションの雛形が作成されること。
  • app.jsではルーターやビューの設定、その他ライブラリのインポートなどを行うこと。
  • routesディレクトリ以下にはルーター(router)が配置され、ルーターは特定のパスへの要求を受理したときに対応する処理へのルーティングを担当すること。
  • 変数resrenderメソッドを使うとテンプレートを使って描画する。
  • viewsディレクトリにはビュー(view)のテンプレートが配置され、これらは描画の際に使われること。

これらは繰り返し実装しないと身につかなさそうだ。

Express web framework (Node.js/JavaScript)の勉強 part3、 express-generator

Express Tutorial Part 2: Creating a skeleton website を読み進める

ジェネレータのインストール

はじめに下記コマンドを実行しジェネレータをインストールする。

npm install express-generator -g

ジェネレータのオプション

ジェネレータは下記オプションを持つ

$ express -h

  Usage: express [options] [dir]

  Options:

        --version        output the version number
    -e, --ejs            add ejs engine support
        --pug            add pug engine support
        --hbs            add handlebars engine support
    -H, --hogan          add hogan.js engine support
    -v, --view <engine>  add view <engine> support (dust|ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)
        --no-view        use static html instead of view engine
    -c, --css <engine>   add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
        --git            add .gitignore
    -f, --force          force on non-empty directory
    -h, --help           output usage information

プロジェクトの生成

ジェネレータを使いプロジェクトの雛形をつくる。
参考元 の例では express-locallibrary-tutorial をプロジェクト名としているので、それに倣う。

$ express express-locallibrary-tutorial --view=pug --git

   create : express-locallibrary-tutorial/
   create : express-locallibrary-tutorial/public/
   create : express-locallibrary-tutorial/public/javascripts/
   create : express-locallibrary-tutorial/public/images/
   create : express-locallibrary-tutorial/public/stylesheets/
   create : express-locallibrary-tutorial/public/stylesheets/style.css
   create : express-locallibrary-tutorial/routes/
   create : express-locallibrary-tutorial/routes/index.js
   create : express-locallibrary-tutorial/routes/users.js
   create : express-locallibrary-tutorial/views/
   create : express-locallibrary-tutorial/views/error.pug
   create : express-locallibrary-tutorial/views/index.pug
   create : express-locallibrary-tutorial/views/layout.pug
   create : express-locallibrary-tutorial/.gitignore
   create : express-locallibrary-tutorial/app.js
   create : express-locallibrary-tutorial/package.json
   create : express-locallibrary-tutorial/bin/
   create : express-locallibrary-tutorial/bin/www

   change directory:
     $ cd express-locallibrary-tutorial

   install dependencies:
     $ npm install

   run the app:
     $ DEBUG=express-locallibrary-tutorial:* npm start

第1引数で指定した名前のディレクトリがカレントディレクトリ以下に作成される。
--gitオプションで.gitignoreも同時に作られる。
この.gitignore は中身が充実しており、これを使うのが好ましいと判断したため--gitオプションを付加して実行した。
以下に expressが作る.gitignore を残す。

expressが作成する.gitignore

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# next.js build output
.next

まとめ

expressを使うことでアプリケーションの雛形を作成できる
引数によってビューアなどを切り替えることが可能
--gitオプションによって.gitignoreも同時に作られる

Express web framework (Node.js/JavaScript)の勉強 part2

Creating route handlersを読みすすめる

前回、特定のパスへのリクエストを受け取り、対応するコールバック関数を実行する方法を学んだ。 今回は express.Router についてを学ぶ

Router

express.Router を使うことで、特定のパスへのリクエストをまとめて扱うことが可能になる。
ここの例では http://localhost:3000/wiki/ 以下へのリクエストを解釈するrouterインスタンスを作成している。
具体例として以下に wiki.js というファイルを示す

wiki.js

// wiki.js - Wiki route module

const express = require('express');
const router = express.Router();

// Home page route
router.get('/', function(req, res) {
  res.send('Wiki home page');
});

// About page route
router.get('/about', function(req, res) {
  res.send('About this wiki');
});

module.exports = router;

4行目 const router = express.Router()

express.Router を使うことでモジュール化されたのマウント可能なルートハンドラを作成できる。
別ファイルで特定のパス(今回は /wiki プレフィックスとする)を指定することで、そのパスへ以下へのリクエストをルーティングするとが可能になる。

7〜9行目 router.get('/', function(req, res){ ...以下略

特定のパス以下の / へのリクエストを第二引数の関数へルーティングする
今回の例では/wikiへのリクエストをルーティングする

12〜14行目 router.get('/about', function(req, res){ ...以下略

特定のパス以下の /about へのリクエストを第二引数の関数へルーティングする 今回の例では/wiki/aboutへのリクエストをルーティングする

16行目 module.exports = router;

ここまでで作成したrouterオブジェクトを別ファイルで使用できるようにエクスポートする

app.js

const express = require('express');
const app = express();
const port = 3000;

const wiki = require('./wiki.js');

app.get('/', (req, res) => {
  res.send('Hello World!')
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}!`)
});

app.use('/wiki', wiki);

5行目 const wiki = rquire('./wiki.js');

nodejsはCommonJSのモジュールシステムを採用している。
* 参考:What is require? | Node.js
CommonJSではrequireを使うことでモジュールを取り込む。
requireはモジュールの名前もしくはパスを引数にとる。

15行目 app.use('/wiki', wiki);

appは関数以外にも、特定のミドルウェアの機能を特定のパスに関連付けることができる。
今回の例ではパス/wikiとオブジェクトwikiが、それぞれapp.useの第一、第二引数に使われている。
/wiki/aboutへの要求はwikiオブジェクトへ渡り、/wiki/aboutからプレフィックス/wikiを除いたパス/aboutを使いwikiオブジェクト内でマッチングが行われる。
結果wiki.jsの12行目で定義されたパスにマッチし、対応する関数が実行される。

まとめ

  • routerは別ファイルにまとめることで、特定のパス以下の処理を分割することができる