さくら VPS のローカルネットワークに接続するインターフェイスを Ubuntu 20.04 で設定する

さくら VPS には同一リージョンの複数サーバーを仮想スイッチングハブに接続することでプライベートネットワークを構築することができます。今回は Ubuntu 20.04 をセットアップした時の設定のメモです。 いつもは OS アップグレードしていたので気づかなかったですが、最近の Ubuntu はネットワークインターフェース設定が netplan というユーティリティに標準は置き換わっています。そのため、その設定の紹介になります。 ローカルネットワークの構築設定 さくら VPS のコントロールパネルで、スイッチを追加して、各サーバーのネットワーク接続画面でそれぞれ追加したスイッチに接続させます。(なお、現状はサーバーはシャットダウンしておかないとこの適用できません。) インターフェースを確認 $ ip addr show でネットワークインターフェースを確認します。 2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 52:54:12:00:XX:XX brd ff:ff:ff:ff:ff:ff inet 153.120.xx.xxx/23 brd 153.120.xx.255 scope global ens3 valid_lft forever preferred_lft forever inet6 2401:2500:102:2b03:xxx:xxx:xx:xxx/64 scope global valid_lft forever preferred_lft forever inet6 fe80::5054:12ff:fe00:xxxx/64 scope link valid_lft forever preferred_lft forever 3: ens4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 52:54:12:00:XX:XX brd ff:ff:ff:ff:ff:ff 4: ens5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 52:54:12:00:XX:XX brd ff:ff:ff:ff:ff:ff 今回は ens で始まる物理インターフェイスの2番目の ens4 をスイッチに接続したものとしてセットアップします。 ...

2020年11月14日 · Toshimitsu Takahashi

自前で JWT を発行、検証を Node.js で行う

Node.js で JSON Web Token (JWT) の ID トークンを発行し、検証する処理のメモです。 JWT のメリットはアクセストークンそのものが有効期間やコンテクストを保持できるため、個別にリクエストごとにセッションIDからセッション情報を引くという機構を省くことができます。そのため、自前の API サーバにおいて、アクセストークンとして JWT の ID トークンを使えるとサーバ処理を省力化できます。 今回の例では、JWT の検証をグローバルにできるようにはせず、サーバ内でのみ行うこととします。一般的な JWT トークンは JSON Web Key (JWK) Set から提供されており、JWT の署名検証ができるようになっていますが、それは行いません。 依存ライブラリ Auth0 がものすごく便利なライブラリを提供してくれています。 https://github.com/auth0/node-jsonwebtoken $ npm i -save jsonwebtoken $ npm i -save-dev @types/jsonwebtoken 鍵の生成 公開鍵と秘密鍵を別々にすることも可能ですが、分けると管理が面倒なのと同一サーバ内で処理するため、PEMファイルで管理します。 $ openssl genrsa > auth.pem 発行と検証処理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import fs from 'fs' import jwt from 'jsonwebtoken' const PEM = fs.readFileSync('auth.pem') function issueToken(subject: string): string { const opts: jwt.SignOptions = { algorithm: 'RS256', issuer: 'https://issuer.example.com', expiresIn: '1d', // https://github.com/zeit/ms } const payload = { sub: subject, } return jwt.sign(payload, PEM, opts) } export async function verifyToken(idToken: string): Promise<object> { const opts: jwt.VerifyOptions = { algorithms: ['RS256'], issuer: 'https://issuer.example.com', maxAge: '1d', // https://github.com/zeit/ms } return new Promise((resolve, reject) => { jwt.verify(idToken, PEM, opts, (err: Error, decoded: any) => { if (err) { reject(err) } else { resolve(decoded) } }) }) } // 実行確認 ;(async () => { const idToken = issueToken('userId') const result = await verifyToken(idToken) console.log(result) })() JWT 署名時の expiresIn より時間が経過している場合は、検証時に TokenExpiredError: jwt expired がスローされます。 時間内であっても発行時よりも maxAge が経過している場合は、検証時に TokenExpiredError: maxAge exceeded がスローされます。 実行結果 1 2 3 4 5 6 { sub: 'userId', iat: 1582639938, exp: 1582726338, iss: 'https://issuer.example.com' } まとめ ログイン成功時に issueToken を呼んで ID トークンをクライアントに発行します。クライアントは API リクエストの Authorization ヘッダに付与して、サーバ側は verifyToken で検証することで、認証および subject(sub)フィールドなどからユーザー情報を取得することができます。

2020年2月24日 · Toshimitsu Takahashi

さくら VPS の Ubuntu 18.04 で LUKS によるディスク暗号化をするには

さくら VPS は、AWS LightSail のようにディスクの暗号化をサービス側でしているという情報がありませんでした。昨今のハードディスク廃棄問題も気になるところで、あまり漏れたくない情報を保存したいのでディスクをまるごと(/boot は除く)暗号化してみます。 Ubuntu 18.04 のインストール コントロールパネルから カスタムOSインストール Ubuntu 18.04 を行います。 途中の「Partition disks」で Guided - use entire disk and set up encrypted lvm を選択します。そしてパスフレーズを設定します。 OS セットアップ後 VPSのコントロールパネルで緑の起動ボタンをクリックして、さらにコンソールボタンメニューのシリアルコンソールも即座に開いておきます。 シリアルコンソール画面がパスフレーズ入力で止まるので、ここで先ほどのパスフレーズを入力して起動させます。 暗号化状態の確認 # cryptsetup status /dev/mapper/vda5_crypt /dev/mapper/vda5_crypt is active and is in use. type: LUKS1 cipher: aes-xts-plain64 keysize: 512 bits key location: dm-crypt device: /dev/vda5 sector size: 512 offset: 4096 sectors size: 208207872 sectors mode: read/write flags: discards 起動時のアンロック設定 現状は起動時にパスフレーズを入力するしかありません。ここにさらに別途鍵ファイルをbootパーティション内に追加して自動起動するようにします。 ここからは Debian/Ubuntuで暗号化 LVM を使いつつ自動起動する - @znz blog の受け売りが大半です。大変参考になりました。ありがとうございます。 ...

2020年2月2日 · Toshimitsu Takahashi

新しい PC に使用していた SSD を流用して Windows 10 をインストールするには

目的 元々データディスク(セカンドドライブ)として使っていた SSD を、新しい PC のシステムディスクとしてデータをすべてフォーマットせずに流用する場合の方法です。本来は一旦別の HDD 等にデータを移動しておくべきですが時間がかかるので、データが大きいのと空き容量がまだので既存データを後方に移動して、前方にシステムディスクとしてのパーティション構成を構築します。 データの移動 EaseUS Partition Master Free をインストールします。 EaseUS®公式サイト|無料のパーティション管理ソフト - EaseUS Partition Master Free 「EaseUS Partition Master」パーティション編集ソフト - 窓の杜 このソフトウェアを使うと、パーティションのサイズ変更が可能になります。それで前方に空きが出るようにパーティションを移動変更します。なお予約済みパーティションなどが先頭にあった場合は削除します。 パーティションを構成する 続いて diskpart コマンドを使います。管理者権限で起動した PowerShell などで diskpart を起動します。 UEFI/GPT ベースのハードドライブパーティション を参考に構築します。 パーティション構成は先頭から下記のようにします。 ESP システムパーティション FAT32 100MB MSR 予約パーティション RAW 16MB 回復ツールパーティション NTFS 768MB Windows パーティション NTFS 残り全部 データパーティション NTFS 元々データ全部 list disk でディスクを一覧します。 select disk 1 で編集するディスクを選択します(ここではディスク1)。 list partition でパーティションを一覧します。この時点でデータパーティションだけ存在するはずです。 ESP システムパーティションの作成 create partition efi size=100 でパーティションを作成します。続けて format quick fs=fat32 label="System" としてフォーマットします。 ...

2019年11月10日 · Toshimitsu Takahashi

さくら VPS の前段にウェブアクセラレータを置いて Let's Encrypt を設定する

さくらの Web アクセラレータは無料枠のある CDN サービスです。これをさくら VPS の Nginx で運用するサイトの前段において、Let’s Encrypt で SSL 化したときの手順をまとめました。 Let’s Encrypt は certbot ツールが便利なのでこちらをインストールします。 SSL 証明書の発行には、ドメイン認証のために http 経由で http://対象ドメイン/.well-known/acme-challenge/ 下にあるワンタイムトークンを取得できるようにする必要があります。このパスは、https 化した後に http アクセスをリダイレクトする場合も除外するようにしておかないといけません。 Let’s Encrypt で証明書を発行 ルートディレクトリの設定 対象ドメインのルートディレクトリを作成します。 # mkdir /var/www/example.tilfin.net Nginx の設定 対象ドメインの server (http) ディレクティブ内に、下記の location ディレクティブを追加します。Nginx をリロードします。 server { listen 80; server_name example.tilfin.net; ... location ^~ /.well-known/acme-challenge/ { default_type "text/plain"; root /var/www/example.tilfin.net; break; } ... } certbot で証明書を発行 # certbot certonly -d "example.tilfin.net" -w /var/www/example.tilfin.net Saving debug log to /var/log/letsencrypt/letsencrypt.log How would you like to authenticate with the ACME CA? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1: Nginx Web Server plugin (nginx) 2: Spin up a temporary webserver (standalone) 3: Place files in webroot directory (webroot) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select the appropriate number [1-3] then [enter] (press 'c' to cancel): ここで 3 を入力します。 ...

2019年9月20日 · Toshimitsu Takahashi

Ubuntu で RAM ディスクを設定して docker をオンメモリで動かすには

自分は Windows PC ホスト内で VirtualBox で Ubuntu 系 Linux Mint を起動しているのですが、docker のコンテナやボリュームデータをまるごとオンメモリで高速に動作させたいと思い、RAMディスクを設定して載せるようにしました。RAM ディスクだと OS をシャットダウンすると消えてしまいますが、Docker の場合、古いコンテナのデータがどんどん溜まっていくために掃除できて開発上好都合でもあります。 RAMディスクの設定 まずパス /ramdisk をマウントポイントとして RAM ディスクを設定することにします。 マウントポイントの作成 ディレクトリを作成して、スティッキービットを付けて誰でも書き込み可能にします。 $ sudo mkdir /ramdisk $ sudo chmod 1777 /ramdisk /etc/fstab の設定 /etc/fstab に下記を追記します。 tmpfs /ramdisk tmpfs defaults 0 0 一旦、OSを再起動します。(fstab に設定しなくても一時的には mount コマンドで対応することも可能) Docker のコンテナとボリュームを置き場を設定 以下の設定中は Docker を停止しておきます。 /ramdisk にディレクトリを作る $ sudo mkdir -p /ramdisk/docker/containers $ sudo mkdir -p /ramdisk/docker/volumes シンボリックを張る /var/lib/docker の containers, volumes が /ramdisk/docker を見るようにします。 $ sudo -i # cd /var/lib/docker # mv containers containers.old # mv volumes volumes.old # ln -s /ramdisk/docker/containers # ln -s /ramdisk/docker/volumes 動作確認 Docker を起動して、適当なコンテナを走らせて、 /ramdisk/docker 内にファイルが作られることを確認します。 ...

2019年9月5日 · Toshimitsu Takahashi

Twitter のアクセストークンをスクリプトで取得する

昨今、Twitter API の App 登録が審査など手順が増えたので、気軽に App を増やさなくなりました。そのため、既存の App に対して管理アカウントとは別のアカウントを認可させたいときがあります。 下記の Ruby スクリプトは gem oauth を使って、サーバを立てずにブラウザだけで認可してアクセストークンを取得するものです。認可後のリダイレクト時に付随するクエリ内容を適切に処理さえすれば、対象の Callback URL が 404 Not found であろうとも問題ないです。一時的にサーバを立てる必要もなく、一連の本来サーバサイドで行う処理をローカルから実行して処理できます。 なお Callback URL が自分の持ち物でないとセキュリティ上問題があるのは当然です。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 require 'uri' require 'oauth' CONSUMER_KEY = '<your consumer key>' CONSUMER_SECRET = '<your consumer secret>' CALLBACK_URL = '<your callback url>' consumer = OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, { site: "https://api.twitter.com", scheme: :header }) request_token = consumer.get_request_token(oauth_callback: CALLBACK_URL) req_url = request_token.authorize_url(oauth_callback: CALLBACK_URL) puts <<EOS Go to the following URL and authorize your account on a browser: #{req_url} Copy the redirected url from the browser and paste it: EOS input = STDIN.gets redirected_url = input.chomp rurl = URI.parse(redirected_url) params = {}.tap do |h| URI.decode_www_form(rurl.query).each do |entry| h[entry[0].to_sym] = entry[1] end end access_token = request_token.get_access_token(oauth_verifier: params[:oauth_verifier]) puts <<EOS =================================== user_id: #{access_token.params[:user_id]} screen_name: #{access_token.params[:screen_name]} Access Token: #{access_token.token} Access Token Secret: #{access_token.secret} =================================== EOS スクリプトを実行すると authorize URLが生成されるので、それを認可したいアカウントでサインイン済みのブラウザに貼り付けて移動します。そのまま Twitter の認可画面でボタンを押すと、本来自分のサービスのURLにリダイレクトされます。この状態でURL欄を中身コピーして、貼り付けます。そうすると続きを処理してアクセストークンして出力します。

2019年9月1日 · Toshimitsu Takahashi

GraphQL DataLoader の実践的な使い方

DataLoader とは GraphQL におけるデータ取得において内部で N+1 処理になるのを回避するためのツールです。例えば、あるリソース(A)一覧とそれに関連付けられた 1:1 のリソース(B)を一気に要求すること場合、最初のリソース(A)リストをクエリした後に、各レコードに対してリソース(B)を毎回クエリすると遅くなります。リソース(B)に対してそのキーをスタックしていき、まとめてクエリすることで N+1 回を 1+1 回に減らすことができます。この機能をまとめたものが DataLoader です。 実践的な使い方 条件検索について DataLoader はあくまでも Key-value キャッシュ付きストアと考えた方がいいです。GraphQL の検索 Query で条件指定する場合は DataLoader を経由せずに直接行います。但し結果はその後の下階層で再度利用する可能性があるので prime メソッドでキャッシュしておきます。 キーによるレコード検索について マスターリソースに対する場合は、単純に主キーで取得およびキャッシュします。 リレーションリソースに対する場合は、複合キーが cacheKeyFn の処理によってユニークな文字列になるように変換します。サロゲートキーは使いません。 要求したユーザーによって結果が変わる場合 この場合は、リクエストコンテキストに応じて DataLoader インスタンスを生成し付与します。コンテキストを生成するミドルウエア内で、DataLoader インスタンス自体をセットするか、それらインスタンスを生成できるメソッドを提供するといいでしょう。 どのレイヤーのデータを DataLoader で扱うべきか GraphQL の Type とデータベースのレコードスキーマが完全に一致せずに、データの変換が必要となる場合の問題です。基本的にデータベースレイヤーに DataLoader を適用して、GraphQL Type には都度変換する方がいいと考えます。この理由としてリゾルバ周りの変更の影響を受けないことや、 N+1 はそもそもデータベース(ストレージ)への負荷であるからです。 コンテキストから DataLoader をどうモデルに渡すか コンテキストはリゾルバのパラメータとして受け取りますが、DataLoader インスタンスは下位レイヤーのモデル(ロジック層やストレージ層)で使いたいので、そのままストレートにモデルのコンストラクタなどで渡すのが一番だと思います。これに引っ張られて Active-Record パターンは JavaScript ベースでは使いずらいと思います。Ruby on Rails 等だとスレッドに情報をセットして、透過的に扱うことも可能ですが。

2019年6月23日 · Toshimitsu Takahashi

DynamoDB DataMapper For JavaScript でインデックス付きテーブルを作成するには

Amazon DynamoDB DataMapper For JavaScript という TypeScript にも標準対応している DynamoDB の JavaScript 向けデータマッパーライブラリがあります。 このライブラリはテーブル作成・削除の機能もあるので、テスト用テーブル作成もこちらを使ってスクリプト化しようと考えました。 ただドキュメントが少なく、グローバルセカンダリインデックスを付与する方法を探すのに手間取ったため、ここにメモしておきます。 スキーマ定義とテーブル作成 プライマリキー(通常のHASHキー)は user_id です。 外部キー(GSIのHASHキー)は email です。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import { attribute, hashKey, table } from '@aws/dynamodb-data-mapper-annotations' @table('users') class User { @hashKey({ attributeName: 'user_id' }) userId!: string @attribute({ indexKeyConfigurations: { 'emailIndex': 'HASH' } }) email!: string } async function createTable() { await mapper.ensureTableExists(User, { readCapacityUnits: 1, writeCapacityUnits: 1, indexOptions: { 'emailIndex': { type: 'global', projection: 'keys', readCapacityUnits: 1, writeCapacityUnits: 1, } } }) } createTable() .then(() => { console.info('Created DynamoDB table users') }) .catch(err => { console.error(err) }) 結果確認 $ aws dynamodb describe-table --table-name users --endpoint-url http://localhost:4569 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 { "Table": { "TableArn": "arn:aws:dynamodb:us-east-1:000000000000:table/users", "AttributeDefinitions": [ { "AttributeName": "user_id", "AttributeType": "S" }, { "AttributeName": "email", "AttributeType": "S" } ], "GlobalSecondaryIndexes": [ { "IndexSizeBytes": 0, "IndexName": "emailIndex", "Projection": { "ProjectionType": "KEYS_ONLY" }, "ProvisionedThroughput": { "WriteCapacityUnits": 1, "ReadCapacityUnits": 1 }, "IndexStatus": "ACTIVE", "KeySchema": [ { "KeyType": "HASH", "AttributeName": "email" } ], "IndexArn": "arn:aws:dynamodb:ddblocal:000000000000:table/users/index/emailIndex", "ItemCount": 0 } ], "ProvisionedThroughput": { "NumberOfDecreasesToday": 0, "WriteCapacityUnits": 1, "LastIncreaseDateTime": 0.0, "ReadCapacityUnits": 1, "LastDecreaseDateTime": 0.0 }, "TableSizeBytes": 0, "TableName": "users", "TableStatus": "ACTIVE", "KeySchema": [ { "KeyType": "HASH", "AttributeName": "user_id" } ], "ItemCount": 0, "CreationDateTime": 1560437300.665 } }

2019年6月13日 · Toshimitsu Takahashi

Node.js アプリに TypeScript を導入する

既存の Node.js アプリケーションを徐々に TypeScript 化していく場合の導入方法のメモです。 Migrating from JavaScript | TypeScript オフィシャルサイトのこちらのぺージが参考になりますが、クライアントサイドを例書かれているので、サーバーサイドは勝手が違います。 typescript と ts-node のインストール まず typescript と ts-node しましょう。 ts-node はコンパイルせずに直接 ts ファイルを実行できます。 $ npm install -g typescript ts-node または -g ではなく --save-dev としてローカルにインストールします。 tsconfig.json の生成 $ tsc --init message TS6071: Successfully created a tsconfig.json file. tsconfig.json 生成されるので、 "target": "es2018", allowJs: true, "outDir": "./dist" にそれぞれ変更します。理由は Promise などを使えるようにするため、JavaScriptファイルはそのまま使うため、コンパイルしたものを dist ディレクトリに出力するためです。 tsconfig.json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 { "compilerOptions": { /* Basic Options */ "target": "es2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ "outDir": "./dist", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "incremental": true, /* Enable incremental compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ } } main ファイルを .ts にする 例えばメインファイルの app.js を app.ts にリネームして、 tsc で実行します。 ...

2019年4月19日 · Toshimitsu Takahashi