2021/07/04

[js]ファイルを分けたい

大きいプログラムを作ると、ファイルを分けたくなるだろう。そうじゃなくても、機能ごとにファイルを分けたいだろうし。
まあ、私が分けたいのだ。


index.ts

01: import * as greet from './greet';
02: 
03: const hello: greet.Greet = greet.hello();
04: if (hello != null) {
05:     console.log(`hello: ${JSON.stringify(hello)}`);
06: }

greet/index.ts

01: export type Greet = {
02:     greet: string;
03:     time?: string;
04: };
05: 
06: export function hello(): Greet {
07:     return {greet: 'hello', time: 'allday'};
08: }
09: 
10: export default {}
  • greet ディレクトリにファイルがあるので、 import の from はそこを指定する。
    • ファイル名が index.ts なのでディレクトリの指定だけで済んでいる。
  • greet/index.ts は export default {} としたので、ファイルの中にある export 指定したものは同じ名前で export される。

という理解で正しいだろうか?

 

import の from より前の部分があまりわかってない。
こうか?

  • 個別に import するなら、 import {個別にコンマ区切り} from '~~'
  • 全部 import するなら、 import 名前 from '~~'

 

greet/index.ts から個別に importすると、こうなる。

01: import {Greet,hello} from './greet';
02: 
03: const msg: Greet = hello();
04: if (msg!= null) {
05:     console.log(`hello: ${JSON.stringify(msg)}`);
06: }

列挙するのが面倒ではあるが、使用しているものがはっきりするので場合によってはわかりやすい。


じゃあ全部 import するので import Hello from './greet' でよいかというと、これはダメそうだ。
Hello.Greet や Hello.hello という指定ができなかったのだ。
「Cannot find namespace 'Hello'」というエラーになる。

namespace を使う場合はこういう書き方になるようだ。
import するときに「import Hello from './greet'」としたが、別に namespace名と同じにする必要はなさそうだった。

あと、namespace も export を指定しないと個別に import できない(個別の話。export default指定しているなら関係ない)。namespace を export しても、namespace の中身が全部 export されたことになるわけではなく、その中でも export を付けたものだけが外部から見えるようだ。

index.ts

01: import Hello from './greet';
02: 
03: const msg: Hello.Greet = Hello.hello();
04: if (msg != null) {
05:     console.log(`hello: ${JSON.stringify(msg)}`);
06: }

greet/index.ts

01: namespace Hello {
02:     export type Greet = {
03:         greet: string;
04:         time?: string;
05:     };
06: 
07:     export function hello(): Greet {
08:         return {greet: 'hello', time: 'allday'};
09:     }
10: }
11: export default Hello;

 

この greet/index.ts を import * した場合は namespace 名を使わなくても済むようだ。

index.ts

01: import * as Pochi from './greet';
02: 
03: const msg: Pochi.default.Greet = Pochi.default.hello();
04: if (msg != null) {
05:     console.log(`hello: ${JSON.stringify(msg)}`);
06: }

 

 

namespace を付けるなら、 export default ではなく個別にした方がよいのかな?と思ったが、そうすると import側でも個別にしないといけなかった。
1ファイルで複数の namespace を入れ込むことは(私の実装だと)やらないので、これは default のままの方が使い勝手がよいか。

index.ts

01: import {Hello} from './greet';
02: 
03: const msg: Hello.Greet = Hello.hello();
04: if (msg != null) {
05:     console.log(`hello: ${JSON.stringify(msg)}`);
06: }

greet/index.ts

01: namespace Hello {
02:     export type Greet = {
03:         greet: string;
04:         time?: string;
05:     };
06: 
07:     export function hello(): Greet {
08:         return {greet: 'hello', time: 'allday'};
09:     }
10: }
11: export {Hello};

 


import の後ろは、 * as xx だったり name だったり {name} だったりといろいろあるが、いったいなんなんだ。

ECMAScript 2015 Language Specification – ECMA-262 6th Edition
https://262.ecma-international.org/6.0/#sec-imports

たくさんあるので from を使うパターンだけ見ていこう。
以下、イタリック&下線の部分はさらに展開されるという意味である。

import ImportClause FromClause;

FromClause の方は種類がないので、先にそちらを。

from StringLiteral

順番に展開すると ModuleSpecifier → StringLiteral なのだが、まあよかろう。
つまり文字列だ。

ImportClause は5種類に分かれる。

  • ImportedDefaultBinding
  • * as BindingIdentifier
  • { ImportsList }
  • ImportedDefaultBinding , * as BindingIdentifier
  • ImportedDefaultBinding , { ImportsList }

たぶん BindingIdentifier は「勝手に割り当てて良い名称」だと思う。bind だから紐付ける名称になるのかな。

上3つが import でよく見る形式で、残り2つはその組み合わせだ。
使えるものを展開してるのはこちら。

import - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import

まあ、こっちの方がわかりやすいな。日本語だし。


これで基本的なところはできたんじゃなかろうか。

もう1つやりたいのが、export するファイルとそうじゃないファイルを分ける作業だ。
実装を見られて困るとかではなく、C言語で言うところのヘッダファイルとソースファイルに分けたいというだけである。

できるのだろうか?
なお、tsconfig.js は isolatedModules:true を指定しているものとする。

 

re-export というのでやってみた。

 

greet/index.d.ts

01: export type Greet = {
02:     greet: string;
03:     time?: string;
04: };

greet/hello.ts

01: import {Greet} from './index.d';
02: 
03: export function hello(): Greet {
04:     return {greet: 'hello', time: 'allday'};
05: }

 

greet/index.ts

01: export {hello} from './hello';
02: export type {Greet} from './index.d';

 

index.ts

01: import {hello,Greet} from './greet';
02: 
03: const msg: Greet = hello();
04: if (msg != null) {
05:     console.log(`hello: ${JSON.stringify(msg)}`);
06: }

うーん、無理やりやりましたという感じが拭えない。
それに呼び出し元の index.ts の方に修正が入るのはダメだ。

 

これは書き方としてダメだった。namespace 内ではダメだそうだ。

01: export namespace Hello {
02:     export {hello} from './hello';
03:     export type {Greet} from './index.d';
04: };
05: export default Hello;

 

これならいけるようだ。

01: import {hello as myHello} from './hello';
02: import {Greet as myGreet} from './index.d';
03: 
04: export namespace Hello {
05:     export const hello = myHello;
06:     export type Greet = myGreet;
07: };
08: export default Hello;

うーん・・・。もっとよいやり方がありそうな気がする。

 

というわけで、あきらめて本を買うことにした。

O'Reilly Japan - プログラミングTypeScript
https://www.oreilly.co.jp/books/9784873119045/

本の中身は書かないのだが、私が import あたりをうまく理解できない理由が分かってきた気がする。

「JavaScript」とか「TypeScript」とかで検索しながらやっていたけど、import というかモジュールの扱いについて過去の経緯がいろいろあり、ネットで見つけた情報はどの時点の JavaScript について書いているかによって変わってくるからだと思う。そういうのを知らずにネットで調べていたので、あっちの情報とこっちの情報をまぜこぜにしてうまくいかなかったとか、そんな感じになったんじゃなかろうか。

本の内容で勉強することの利点は、よくも悪くも情報が混ざらないことだと思うので、あやふやな知識でふらふら調べるよりはましなんじゃなかろうか。せっかくお金出したんだから、ましであってほしい。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。

注: コメントを投稿できるのは、このブログのメンバーだけです。