英語のパターンプラクティスについて
去年、「英語のハノン 初級」をやってみて、瞬間英作文よりパターンプラクティスが自分にはあっていそうだと感じました。 改めてパターンプラクティスについてまとめてみます。自分の個人的な事情がかなり混ざってます。 目次 パターンプラクティスとは? 以下のように、文を少しずつ変化させながら反復練習する学習法です。 I work in an office. ↓ Question Do you work in an office? ↓ She Does she work in an office? ↓ Yesterday Did she work in an office yesterday?瞬間英作文との違い 瞬間英作文とは? 瞬間英作文は 英語上達完全マップ(← クリックすると瞬間英作文の解説ページに移動します)で提案されて知られるようになった、「話せるようになるための」訓練法です。今では広く知られるようになりました。 @amazon(4860641027, https://m.media-amazon.com/images/I/71-pbVzgYYL._SY425_.jpg) @amazon(4860641345, https://m.media-amazon.com/images/I/81b78hGquyL._SY342_.jpg) 瞬間英作文が苦手な理由 まず前提ですが、 私の中では「瞬間英作文」であって「瞬間英訳文」ではないです。 ❌️ 日本語 → 英語 ✅️ 日本語 → 言いたいことのイメージ → 英語 日本語を英訳するのではなく、日本語からまず言いたいことのイメージを頭の中に作り、その「言いたいこと」を瞬間的に英語にする練習です。 この「言いたいことのイメージ」を作るのが苦手なんです。これを瞬間的にできている気がしないですし、どうしても日本語にひきずられている感じがします。 ❌️ 私は今日の朝に公園でジョギングをしました。 ✅️ 私、ジョギングしたんです、公園で、今日の朝に。 というように、英語の語順で日本語を書いてくれたら、英訳するにしても英語の語順に慣れるのでまだマシなんですが・・・。 日本語って英語みたいに語順に厳格じゃないので、スラッシュリーディング^1が長文読解に有効なように、それを活かしたらいいのにと思います。 対してパターンプラクティスは日本語が介在しないので、英語を英語のまま理解して、英語で反応する練習になります。 理論的背景 パターンプラクティスは理論的背景があってグローバルに活用されていた手法^8です。対して瞬間英作文は日本人の英語教室講師が実践に基づいて提唱したもので、明示的に理論背景を提示しているわけではないようです(理論に従っていたら正しくて効果的というわけではないです)。 外国語習得法の歴史については、以下の過去記事をご覧ください。 [[language-acquisition-methods]] パターンプラクティスは「古典的」な教授法です。何の効果があって、何が問題だったのかある程度明らかになっています。 正しい文法を無意識に使えるようにする効果は見込めそうです。 ただし それだけでは話せるようにならない という批判から、その後に廃れていきました。 一方、(世界に似たような研究はありそうですが)瞬間英作文は21世紀になって日本で流行った独自の方法です。個人の経験ではなく、英語講師が(他の訓練と組み合わせて)複数人に効果があることを確認している方法ではあります。 瞬間英作文に対して一番多い批判は「日本語を英語に翻訳する練習をしても話す練習にならない」というものです。英語を話す時に❌️ 言いたいこと → 日本語 → 英語というように間に日本語を挟んでいたのでは、流暢に話せるようにはならないからです。 特に理論的背景を提示しているわけではないようだと書きました[^2]が、 [^2]: 英語上達完全マップの瞬間英作文の解説 には、「まず言いたいことのイメージを作る」という説明はありません。✅️ 日本語 → 言いたいことのイメージ → 英語というように先に「言いたいことのイメージ」を作るのであれば、スピーチ・プロダクション・モデルという、発話産出についての心理言語学のモデルに合致したやり方になります。 スピーチ・プロダクション・モデル(speech production model)とは、「何を話そうか」という(まだ特定の言語になっていない)抽象的な思考を抱いてから、それを実際に口を動かして音としての言葉として発するまでの過程を説明したものです。有名なのはウィレム・レヴェルト(Willem Levelt)が提唱したモデルです[^3]。 [^3]: 英語で"speech production model"を検索すると、色々なもっと複雑なモデルが沢山見つかります。 %%{ init: { "width": 320 } }%% graph TD A["概念化 (Conceptualization)"] --> B["構文化 (Formulation)"] B --> C["発話 (Articulation)"]ではこの練習をする時に「概念化」をどうするか?です。 英会話教室なら、コミュニカティブ・アプローチのように他の人と意図のやり取りをする状況を用意するのがその一つの方法です。 独学でやる教材を作るとすると、母国語を一切使わないアプローチなら絵を使ったり動画を使ったりなどが考えられます。しかしそれだと絵や動画で表せることに限られてしまいますし^4、練習問題を作るのが大変です。 瞬間英作文は独学でこの練習をするのに、お手軽で便利な方法なんですね。 パターンプラクティスとしても、「言いたいこと」として言うように意識することでスピーチ・プロダクション・モデルに沿った練習ができ、過去に批判された「機械的で受動的な練習になりがちで、自分から言いたいことを生成するプロセスが欠落しやすい」という欠点をある程度補えると思います。 独学でのパターンプラクティスは中級以上向け 今の自分の意見では、最初はインプット中心、徐々に真似をして口に出す練習を増やしていき、パターンプラクティスや瞬間英作文で文法の自動化を目指すのは中級以上になってからで良いと思っています。 まとめて「アウトプット」と括られてしまいますが、アウトプットには大きく3つの段階があると考えます。インプットも含めると以下です。インプット シャドーイングやリピーティングなど、聞いたものを真似して口に出す(英文があれば、それを英語の音として口に出せる) 言いたいことを口に出す(言いたいことを英語にできる) 実際の会話で使う(英語でコミュニケーションできる)1 はリスニング力を高めるためのインプット強化の側面もかなり強いです。特にシャドーイングはそうです。 ここでのパターンプラクティスや瞬間英作文は 2 の段階の訓練法です。 英語学習に限らず、大抵の物事はこんなに綺麗に区切って段階的に進めた方が良いわけではないです。しかし少なくとも量としては、初級ほど前半が多く、レベルが上がるに従って後半を増やしていった方が良いと思います^5。 もし中級より前の段階から文を作る練習をしたいのなら、パターンプラクティスよりも瞬間英作文の方がレベルが低くても開始しやすいと思います。 パターンプラクティスには易しい教材がないので・・・。 瞬間英作文との共通点 瞬間英作文もパターンプラクティスも、反復練習で考えなくてもできる自動化を目指す点では共通しています。 語彙や表現を自動化するのにも使えますが、特に文法を自動化するのに効果的な点も同じです。 パターンプラクティスのデメリットと対策教材を作るのが瞬間英作文よりも難しく、選択肢が少ない 機械的な反復練習なので飽きやすい 無味乾燥な例文を単に機械的にパターン変化させていくだけだと、実際の会話で使うイメージが湧きにくい1 については後述します。 2 については、瞬間英作文も同様です。これをメインの学習に据えるのではなく、1日の学習時間の中で10〜15分など、短時間を毎日やるのが良いと思います。 3 については、「実際にこれを言いたい気になって言う」というやり方をすべきだと思います。単に機械的にパターンを置き換えた正解を言うのではなく、それを言いたい伝えたいという気持ちで口に出すと効果が上がるはずです。 とはいえ最初は無理だろうと思います。しかし自動化してくれば段々意識しないで使えるようになるわけですから、むしろ言いたい伝えたいという気持ちで言える余裕ができるまで続けるのが目標になるのではないでしょうか。 独学教材 元々、パターンプラクティスについては知っていました。 ただ次に説明する「先生に習う」タイプのコースしか存在しないと思っていたんです。 なので「英語のハノン」の存在を知った時に驚きました。独学できる教材が出てきたのだと。 とはいえ、独学できる教材は少なくて、自分が知っている範囲では以下の2つだけです。 瞬間英作文に比べて、パターンプラクティスの教材を作るのは難しいのだと思います。教材名 概算価格 形式 特徴英語のハノン 約2,000円/冊 書籍+音声ダウンロード 圧倒的コスパ。パタプラ 約50,000円〜/アプリ 買い切りアプリ ビジネス特化。復習アルゴリズムが優秀。英語のハノンのコスパに驚愕します。自分のレベルに合わせて順に買っていけばいいですし。初級だけでも真面目に身につくまで何周かしたら1年はかかりそうですし。 人気教材になったので「初級」「中級」「上級」「フレーズ編」「ロジック編」「フレーズ編 Life goes on!」と種類が追加されていっています。フレーズ編第2段の Life goes on! は先月末に出たばかりです。今後もまだ増えるかもしれません。 自分はまだ初級しか買っていませんが、フレーズ編のレビューを見ると、意味のあるダイアログでのロールプレイ練習も取り入れているようです。 一方でパタプラ イングリッシュは高価に思えますが、オンライン英会話のコースを受けるよりは(ちゃんと身につくまでやるなら)トータルで安く済むでしょう。アプリで1日15分とか手間なく手軽にやれそうです。エンジニアで英語学習に取り入れている人の記事をたまに見かけますが、特にビジネスで実際に英会話が必要な方に好評のようです。私もビジネスで実際に英語が突然必要になったら、パタプラをやると思います。 60日間のお試しができます。しかし「パタプラ句動詞&スモールトーク」「パタプラ会議&ファシリテーション」「パタプラ口語表現」と他にも同額程度の教材があり、全部買うと大変なことに・・・。 英語のハノンを発売日順に並べると以下になります。クリックすると Amazon の商品ページに飛びます。 @amazon(4480815821, https://m.media-amazon.com/images/I/617gdIbyreL._SY425_.jpg) @amazon(4480816852, https://m.media-amazon.com/images/I/71Vu3lu25eL._SY425_.jpg) @amazon(4480879137, https://m.media-amazon.com/images/I/51qWzefymSL._SY445_SX342_PQ99_.jpg) @amazon(4480816887, https://m.media-amazon.com/images/I/51mlFzn-WkL._SY445_SX342_PQ99_.jpg) @amazon(4480816992, https://m.media-amazon.com/images/I/61ybgaqX6rL._SY425_.jpg) @amazon(448081700X, https://m.media-amazon.com/images/I/61u7InqJQ3L._SY425_.jpg) やる順番は 初級 → フレーズ編 → フレーズ編(Life goes on) → 中級 → 上級 → ロジック編 の順番が良さそうに思えます。 ただ私は中級以上はたぶんやらないと思います。フレーズ編もやらず、イマージョンラーニングで何度も Passive Listening ^6 した題材でフレーズの練習をやりそうな気がします。 先生に習う 元々パターンプラクティスは教師が介在する教授法です。 有名なのはカラン・メソッド(イギリス英語) DME メソッド(アメリカ英語、カラン・メソッドより新しく、発展系の位置づけ)でしょうか。 これらのカリキュラムは初級者からでも始められるように設計されています。 カラン・メソッドは幾つかのオンライン英会話スクールで提供されています。 DME メソッドは提供しているところが2026年1月時点で2つしか見つかりません。イングリッシュベル と iTalk English が提供しています。 パターンプラクティスを続けるコツ パターンプラクティスの欠点は退屈なことです。短期で叩き込むのが必要な必死な状況でない限り、無理して長時間やらずに継続が苦じゃない範囲で毎日続けるのが良いと思います。 英語のハノンの著者も長時間やらずに1日1ドリルを毎日続けるようにと言っています。参考: 『英語のハノン』使い方まとめ|著者公式Xよりその主張は書籍名にも現れています。「ハノン」はピアノの定番基礎練習本です。指のメカニカルな動きを身につけるための(超つまらない)超基礎練習としてウォームアップ的に最初にやり、その後に練習曲^7、課題曲をやるのがよくある練習サイクルです。 個人的な要望 「英語のハノン 初級」は優れた教材ですが、初級とか言いながら全然初級者向けじゃなくて、中級以上の人向けです。 ターゲットは「TOEIC の点は十分高いのに話せない人」なんでしょう。 自分は中級者だと思うんですが、ついてはいけます。レベルが合っていないとは思いません。でも中級であっても、もっと簡単なところから始めたいと思っています。 楽器の練習でもそうですが、基本的に練習というのは意識を1つのことに向けた方が効果的です[^9]。複数のことを組み合わせる場合も、その「組み合わせること」に意識を向けます。組み合わせる各要素はさほど意識を向けなくてもできるようにしておきます。 なので文法の練習をする最初の段階では、そのターゲットにしている文法以外のこと・・・語彙のこととか、長い文を脳に記憶する苦労は邪魔になります。 それらは簡単な文でその文法をある程度身に着けた後に発展としてやるべきじゃないでしょうか。 [^9]: 専門でない自分でも心理学の考え方に「注意資源の有限性」があるのは知っています。言語習得に関する研究でも、ピーター・スキーハン(Peter Skehan)の「注意容量制限モデル(Limited Attentional Capacity Model)」というのがあるそうです。 なんというか、「これをやるだけで」みたいな一石二鳥三鳥を狙った教材に思えます。これをやるだけである程度語彙やチャンクも覚えられるとか、わざと途中で少し長い文を入れたりして、脳に英語を短期記憶させる能力を上げるようメリハリをつけてるように思われます[^10]。が、自分はそういった幾つかの要素を組み合わせることに意識を向けて練習するのは、それぞれ単体がある程度できるようになった後がいいです。 [^10]: スキーハンの主張とは逆に、ピーター・ロビンソン(Peter Robinson)の「認知仮説(Cognition Hypothesis)」というのがあり、あえて負荷をかけたほうが言語発達が促されると主張しています。正直、どっちの側面もありそうに思えますが、最初は「注意容量制限モデル」で、その後に「認知仮説」で練習したいです。 教材は変化しないので、何周もしたら内容を覚えてしまいます。 内容を覚えてしまったのではパターンプラクティスの効果が弱まりそうです[^14]。 それよりは同じ文法項目を、まずはもっと簡単な文で身につける。次に同じ文法項目をもうちょっと難しい文で身につける。という風に段階的にレベルアップできる方が嬉しいです。 [^14]: 特定の学習データに対して過度に慣れてしまうと、AI の学習でも「過学習」というのが発生します。学習につかったデータに対しては精度が上がっていくけれど、未知のものに対しての精度は逆に落ちていくという現象です。 使われている語彙や文法が Duo Basic くらいの、中学レベルの「英語のハノン 超基礎編」が欲しいです。 自分にとっては「英語のハノン 初級」は、ハノンというより練習曲に近いんですよね・・・。 Basic Grammar in Use のパターンプラクティス教材があったら、泣いて喜ぶんですが・・・。 @amazon(052113353X, https://m.media-amazon.com/images/I/41a3ceIROeL._SY425_.jpg) パターンプラクティスは必要か? 必ずしも必要だとは思いません。パターンプラクティスにせよ、瞬間英作文にせよ、それらをやらなくても話せるようになる人はいます。 下記の 1 と 3 をやっていれば、2 は自然にできるようになる人もいるわけです。シャドーイングやリピーティングなど、聞いたものを真似して口に出す(英文があれば、それを英語の音として口に出せる) 言いたいことを口に出す(言いたいことを英語にできる) 実際の会話で使う(英語でコミュニケーションできる)ただし文法を無意識に使えるようにするための効果的な訓練法ではあると思いますし、苦手な文法の抜け漏れも防げます^12。やることで習得が早まる可能性は十分ありそうです。 また日本人はシャイなので、(私もそうですが)実際の会話の前にある程度言いたいことを言えるようになっておきたい人も多いでしょう。そういう人には 2 の訓練法として有効だと思います。 日本人にとっての英語は全てにおいて遠い言語です。特に音が難関だと思うものの、文法も相応に難関^13です。 世界の第二言語習得方法の中心からは外れてしまったとはいえ、日本人が英語を話せるようになるためという条件では、パターンプラクティスや瞬間英作文は他の方法と組み合わせる補助として役に立ちそうに思われます。 私もメインの学習法にするつもりはなく、あくまで補助的に必要と思ったタイミングでのみ利用するつもりです。 まとめ パターンプラクティスは、頭でわかっている文法やチャンクを「無意識に使える状態」にするための強力な訓練法です。パターンプラクティスの利点: 日本語を介さず、英語の語順や構造をそのまま脳に定着させられる。 教材選びの注意点: 『英語のハノン 初級』であっても、実際には中級者以上のレベルが要求される。 継続のコツ: 1日15分程度の「短時間の習慣化」が挫折を防ぐ鍵。瞬間英作文がしっくりこなかった、日本語の介在が邪魔と思う方は、パターンプラクティスを試してみてはどうでしょうか。まずは『英語のハノン 初級』を1日1ドリル、他の方法をやる前にウォームアップとして取り入れることから始めてみては?
外国語習得法の歴史から、良いとこ取りを考える
自分は英語学習にイマージョンラーニングを取り入れています。また、去年「英語のハノン 初級」をやってみて、瞬間英作文よりパターンプラクティスが自分にはあっていそうだと感じました。 そこで改めて、語学教授法の歴史を振り返り、各教授法の特徴を比較・考察してみます。 教授法というのは教師が生徒に言語を教えるための方法論ですが、ここでは独学手法であるイマージョンラーニングも含めて自分なりに考察しています。 そういう意味でタイトルに「語学教授法」ではなく「外国語習得法」という言葉を使いました。 専門家ではないので細かいところで正確ではないかもしれません[^10]。 [^10]: 大筋としては間違っていないように気をつけたつもりです。一応 AI (Gemini 3.0, GPT-5.2, Perplexity) にもチェックしてもらいました。特に気になるものについては自分で調べてみてください。 「どう取り入れるか?」については、あくまで現時点の私がどう取り入れるか?という個人的な考えを書いています。皆さんはご自身にあった方法を考えてみてください。 目次 自分の外国語習得方法の歴史観 先に全体像を示しておきます。あくまで自分の中での大雑把なまとめです。文法訳読法 「言葉は分析するものだ(文書から知識を得るための教養)」ダイレクト・メソッド(1800年代後半〜、第二次産業革命以降) 「いや、母国語は使うな。言葉と意味を直接結びつけろ(幼児が言葉を覚えるように)」アーミー・メソッド(第二次世界大戦中) 「言葉は、ネイティブを徹底的に模倣して体に叩き込むものだ(戦地で生き残るために短期間で叩き込め!)」 → パターンプラクティス登場オーディオリンガル・メソッド(1950年代後半〜1960年代) 「言葉は、構造を反復して身につける習慣形成だ」 → パターンプラクティスの理論化コミュニカティブ・アプローチ (1970年代〜) 「いや、言葉は目的を達成するための道具だ(通じれば文法が多少間違っていてもいい)」クラッシェンの5つの仮説 (1970〜80年代) (ナチュラル・アプローチ) 「いや、言葉は『意味』を理解していれば、脳が勝手に獲得するものだ(練習よりインプットだ)」認知言語学 (1980年代〜) (コア・イメージによる理解) 「いや、言葉は人間の体や感覚に基づいたイメージだ(丸暗記じゃなく、ネイティブの『世界の捉え方』を納得して身につけろ)」イマージョンラーニング (2000年代〜) 「じゃあ、YouTube とか Netflix とかで大量のインプットをすれば、独学できるよね?」graph TD A[文法訳読法] -.-|批判: 喋れない| B[ダイレクト・メソッド] B -->|+反復練習| C[アーミー・メソッド] C -->|教室でやりやすい形に| D[オーディオリンガル・メソッド] D -.-|批判: 機械的過ぎて意味や意図を無視している| E[コミュニカティブ・アプローチ] D -.-|批判: 人は言語を獲得する能力を生まれながらに備えている| F[クラッシェンの5つの仮説] F -->|教授法| G[ナチュラル・アプローチ] F -->|独学法| H[イマージョンラーニング] I[認知言語学] -.-|別側面からの支持: 言葉は認知能力の一部| F I -->|学習法への応用: イメージ学習| J[コア・イメージによる理解] B -.->|精神的継承: 幼児が言葉を覚えるように| F E -->|折衷案: 必要に応じて文法に注意を向ける| K[フォーカス・オン・フォーム]後から登場したものはその前に登場したものの発展だったり批判から生まれたりしています(コミュニカティブ・アプローチとクラッシェンの5つの仮説は同時期に登場)。 しかし後から登場したものが必ずしも優れているわけではありません。 それぞれの特徴と、どういった批判があって違うものになったのか?、前のものからどう発展したのか?を知ることで、うまく組み合わせて自分にあった学習法を見つけられるのではないかと考えています。 なおここでのイマージョンラーニングはカナダなどで行われているイマージョン教育とは異なり、教師なしで独学するための方法論としてのイマージョンラーニングを指します。Refold で紹介されている方法論です(皆さん、カスタマイズされていますが)。 先にまとめ この記事は長文なので、先に結論を書いておきます。自分は以下を取り入れます。 前提:動機(目的)をはっきりさせておく 時間をかけることは必要方法として取り入れること:独学に向くイマージョンラーニングを中心に据え、実践的で意味のある大量のインプットを受け取る 文法訳読法はもう十分なので、必要に応じて文法的知識を補う できるだけ日本語を介さずに、英語をイメージと直接結びつける とはいえ、日本語による理解を完全には排除しない 語彙のコア・イメージを理解しておく 文字よりも音を重視する ネイティブの発音やリズム、イントネーションを真似して口に出す練習をする イマージョンラーニングで出会った「状況における意味のある内容」を、自分の「意図」として伝えるつもりで真似する パターンプラクティスを取り入れて文法や表現を反復練習で自動化する パターンプラクティスで行う反復練習内容について、説明による理解を排除しない パターンプラクティスで言葉を発する際に、その文を自分の「意図」として伝えるつもりで言う 英語を道具として使う意識を持つ 文化・歴史・政治など、言語の背後にあるものも学ぶ^100文法訳読法 文法訳読法は、「単語と文法知ってたら自分の母国語に訳して理解できるじゃん」という、昔から日本の学校教育でも取り入れられている外国語教授法です。 でも実際に話せるようにはならない、という問題があります。 昔々は会話ができることよりも、外国の情報を書物で読むことが重要視されていました。 「文法訳読法」は、外国語の文章を読んで理解できる(書ける)ようにするには、最短の学習時間で済む超効率的な方法です。 ダイレクト・メソッド (1800年代後半〜) 産業革命が起こって近代化し交通網が発達すると、「実際に話せる言葉」としての外国語習得が求められるようになりました。 1800年代後半に、それまでの文法訳読法への批判から、母国語を使わずに外国語だけで教える教授法が考案されました。 幼児は母国語を音から自然に習得します。これを模倣しようという発想が基盤にあります。ただし後で紹介するナチュラル・アプローチのように、幼児が言葉を覚える過程を完全に模倣しようとはしていません。 母語を排除してターゲット言語とモノを結びつける仕組みを模倣しますが、教師が特定の単語や文型を順番に提示して教えるので、プロセスは自然ではなく意図的です。ベルリッツ・メソッドは、19世紀末に開発されたダイレクト・メソッドです。 ただし現在のベルリッツ社が提供しているものは、あくまでコアとしているだけで、現代的にアレンジされていると思いますが。 しかし実際に存在するモノはターゲット言語と結びつけやすいですが、 抽象的な概念や動詞、文法構造などは説明なしに理解するのは難しいです。 アーミー・メソッド (第二次世界大戦中) 第二次世界大戦中、アメリカ軍が外国語を短期間で習得するために開発した教授法があり、劇的な成果をあげたそうです。 ASTP (Army Specialized Training Program、陸軍特殊訓練プログラム)です。その中に語学に特化したクラスがありました。アーミー・メソッドとも呼ばれます[^19]。 [^19]: 日本ではアーミー・メソッドという記述を見かけますが、英語圏では一般的な呼び方ではないようです。基本的に ASTP と呼ばれています。ここでは一般人に分かりやすいようにアーミー・メソッドと呼びます。 ダイレクト・メソッドの考え方を基盤にしつつ、「反復練習(パターンプラクティス)」を掛け合わせました^21。 ASTP(アーミー・メソッド)も一般的には次に紹介するオーディオリンガル・メソッド(の早期バージョン)とみなされるようですが、ここでは説明しやすくするために区別しています。 オーディオリンガル・メソッド (1950年代〜) アーミー・メソッドの流れを組むのがオーディオリンガル・メソッド(Audio-Lingual Method、ALM)です。 戦後に一般教育向けに理論整理されました。 この教授法の中心的な技術がパターンプラクティスです。 以下が、現在も受けられる代表的な教授法です。カラン・メソッド(イギリス英語) DME メソッド (アメリカ英語。カラン・メソッドの発展系の位置づけ)ベルリッツ・メソッドが自然な会話や絵や実物を使って「赤ちゃんが言葉を物事と結びつけるやり方の模倣」を目指したのに対し、オーディオリンガル・メソッドは反復トレーニングで条件反射的にできるように習得することを目指しています。 この手法の結果に対する批判どうこうの前に、一番のデメリットは機械的な反復練習が退屈ということではないでしょうか。 コミュニカティブ・アプローチ (1970年代〜) オーディオリンガル・メソッドでは自然な会話ができるようにならない、という批判が1970年代に高まり、コミュニカティブ・アプローチ(Communicative Approach)が登場しました。 このアプローチでは、言語はコミュニケーションの手段であり、文法や発音の正確さよりも伝えたいことが伝わることが重要だと考えます。 オーディオリンガル・メソッドが「正しい文章を言うこと」をゴールにしていたのに対し、コミュニカティブ・アプローチは「情報の隙間(インフォメーション・ギャップ)」を埋めることを重視します。 例えば二人の生徒に少しずつ内容が違う2枚の絵を渡し、言葉だけでその違いを探させます。 答えを知らないので、伝える「必要性」が生まれます。 基本練習より練習試合を重視するスタイルに変わったという感じです。 このアプローチは英会話教室において主流になりました。 逆に言えば、英会話教室にとって都合が良かった方法論とも言えそうです。教師の負担が少なく、生徒の継続性も高いからではないでしょうか。 これに対する批判は、当然のように「正確な言語運用能力が身につかない」というものです。 これを中心に据えるとしても、やはりこれだけでは不十分なのでしょう。今は「フォーカス・オン・フォーム」(意味のあるやり取りの中で、必要に応じて文法に注意を向ける)が推奨されているようです(後述)。 クラッシェンの5つの仮説 (1970〜80年代) ダイレクト・メソッドの「幼児は母国語を自然に習得する」と考え方は同じです。それに理論的な裏付けを与えた形になります。 「生得説」=「人は言語を獲得する能力を生まれながらに備えている」という考えに基づいています。 クラッシェンの仮説をもとに、より幼児の母国語習得過程を真似たナチュラル・アプローチという教授法が開発されました。特徴 ダイレクト・メソッド ナチュラル・アプローチ共通点 母国語(翻訳)を介さない 母国語(翻訳)を介さない教え方 教師が絵やジェスチャーを使い、意図した文型を練習させる 理解可能なインプットを大量に与え、自然な発達を待つ幼児の模倣 「結びつけ方」の模倣 「獲得プロセス」の模倣習得-学習仮説 大人が第二言語を獲得する場合、潜在意識的な「習得」 と、意識的な「学習」 の2つの言語発達プロセスがあると考えます。 潜在意識的な 「習得」の方が遥かに重要 です。 「習得」プロセスは子供が母語や第二言語を身につけるプロセスと良く似ています。 他の仮説の前提となっています。 自然習得順序仮説 言語のどの構造が早期に習得され、どれが後期に習得されるかについては、習得者の間に類似性が見られます。 意識的な「学習」の順序は通常は単純なものから複雑なものへ進むけれど、これは「習得」による順序と同じではない可能性があります。 (例: 英語なら、例えば三単元の s は進行形より早期に学ぶけれど、習得するのは難しくて後になります) モニター仮説 知識を学ぶ「学習」は自分が間違えたかどうかをチェックするモニターの役目しか果たさないというものです。 学習で得たモニターの使用には条件があります。時間的余裕 正確さに意識を向けている 規則を知っている3つの条件が全て満たされる状況はまれです。 特に話している時には時間的余裕も正確さに意識を向ける余裕もないです。モニターに意識を使いすぎると流暢さが阻害され、会話がぎこちなくなります^30。 役に立つのは、読み書きのとき 話す前に考えるとき 話した後で自分の発言を振り返るときです。 言語を話せるようになるには、意識しなくても直感的に使えるように「獲得」する必要があります。 それには学校で学ぶような「知識」を考えながら使えるようにする「学習」はほとんど役に立たないという仮説です。 しかも意識しなくてもよいレベルになれば自分の間違いにも直感的に気付けるので、会話中でもモニターの役目を果たせます。 3タイプの学習者がいるそうです。過剰使用者。全てのことについて「規則を知っている」必要があり、第二言語習得において文法感覚を信頼していない。 極端な過小使用者。第二言語から「拾う」ことができるものに完全に依存している。誤り訂正の影響を受けず、文法テストの成績は悪い。しかし目標言語を大量に習得でき、しばしば非常に複雑な構文も使用する。 最適な使用者。あくまで「学習」を「習得」の補足として適切な場合に使用する。ライティングにおいてはネイティブ並を達成する可能性がある。コミュニケーションの邪魔にならないように、習得されたもののギャップを埋めるために使用する。インプット仮説 「インプット仮説」は、理解可能なインプットを大量に受け取ることで言語は無意識に理解できるように「獲得」されるという理論です。 実際には成長していくために「ほとんどが理解できるけれど、新たに得られるものがある」のが好ましいです("i + 1" と表現されます)。 情意フィルター仮説 言語適性(頭の良さ)よりも、動機、やる気、楽しい、夢中になっている、などの情意の方が「習得」に関係が強いという仮説です。 ただしモニターとして「学習」が使えるので、言語適性も無関係ではないとのこと。 以下、クラッシェンのアドバイスです。もしあなたが頭でっかちタイプなら、「学習」に偏らずに「習得」をメインにするように気をつけたほうが良いです。 逆に学校の成績が良くなくて自信がない人は、「習得」には関係ないので気にしなくて良いです。 ただし幾らか文法の「学習」をした方が、正確な英語を使えるようになるでしょう。反論その1:アウトプット仮説 各仮説には反論もあります。ここでは特に気になる、インプット仮説に対するメリル・スウェイン(Merrill Swain)の反論を取り上げます。 英語とフランス語の地域があるカナダでは、もう一方の言語で授業が行われるイマージョン教育が行われています^40 しかしアウトプットが十分でないと、リスニングはネイティブにかなり近い水準になるものの^50、スピーキングには目立つ課題が残るそうです。 なので「アウトプットも必要」というのがアウトプット仮説です。 勘違いしないで欲しいのは「インプットが重要」というのは支持しています。 アウトプットがインプットの質をより高める、という考え方です。気づきの役割: 自分が「伝えたいこと」と「実際に言えること」の間のギャップ(欠落)に気づくプロセス 「言えない!」という壁にぶつかることで、脳が不足している知識を特定し、その後のインプットに対する感度(注意)が高まる(「あ、これ言えなかったやつ」) アウトプットが次のインプットをより「理解可能」かつ「吸収可能」なものにする準備段階として機能する仮説検証機能 アウトプットは「この言い方で通じるかな?」という自分なりの言語的仮説を試す場 相手の反応や訂正を通じて、自分の知識が正しいかどうかを確認し、修正していくことができるメタ言語的機能 言葉を何とかひねり出す過程で、言語そのものについて意識的に考え、言語体系を内面化・整理する効果がある反論その2:インタラクション仮説 単に聞くだけ(インプット)ではなく、会話の中で「今のどういう意味?」「こういうこと?」と意味をやり取り[^51]をすることが、最も理解可能なインプットを効率的に生み出すという反論です。 こういったやりとりをすることで、相手が自分の理解度に合わせて言い換えたり、スピードを落としたり、補足説明を加えたりしてくれます。難しかったインプットが自分にとっての「理解可能なインプット(i+1)」に作り替えられます。 [^51]: 意味の交渉(Negotiation of Meaning) これは独学者にはなかなか厳しい指摘です。 コミュニカティブ・アプローチとクラッシェンの仮説が同時期に提唱された背景 1960年代までの「文法中心・ドリル中心」の教え方が、「テストの点数は良いのに、実際には全く話せない学習者」を大量生産してしまったという反省があったからのようです。あれ?どこかの国の学校教育でも聞いた話ですね・・・。 しかし日本の文法訳読法中心の学校教育とはちょっと違います。アウトプットの練習はしていたのです。 「文法は完璧だけど、実際何て言えばいいか分からない」「練習ではペラペラなのに街角で話しかけられてもフリーズする」学習者が大量に生まれたようです。コミュニカティブ・アプローチの主張 「実際に使う場面を想定して練習しないと、使えるようにならないぞ」クラッシェンの仮説の主張 「ドリルなんて不自然なことはやめて、もっと自然に意味を理解することに没頭しよう」どちらも「意味のある言葉」を重視した点で共通しています。 オーディオリンガル・メソッドに対する「文法(形)ばかりで、中身(意味)がない!」という批判です。 ただアプローチの仕方が面白いほどに違います。コミュニカティブ・アプローチ(機能重視) 「レストランでの注文」「道案内」など、その状況で伝えたい意図[^60]ごとに、多少間違ってもいいから「やり取り」を重視する。クラッシェンの仮説(獲得重視) 「リラックスして面白い物語などを大量に聞いたり読んだりして、意味を理解(インプット)すれば、脳のスイッチが勝手に入る」と主張。[^60]: 専門用語で Function(機能) と呼ばれるようです。「その言葉を使って、何を成し遂げたいか(意図)」を指します。依頼する、謝罪する、誘う、不満を言う、などです。 認知言語学 (1980年代〜) クラッシェンは「生得説」=「人は言語を獲得する能力を生まれながらに備えている」に基づいて、「だから頑張って学習したり練習したりしなくても、大量のインプットがあれば脳が勝手に言語を獲得する」と考えました。 その「生得説」を当時支えていた考え方は、ノーム・チョムスキーらの提唱していた「生成文法」でした^25。 これは「全ての言語の元となる普遍文法があり、それを処理する装置が脳内に生まれながらに備わっている」という考え方です。 私はこの意見を支持しません。人の(というか他の動物も)持っている様々な認知能力と学習能力の組み合わせで言語を獲得しているのではないかと考えています。 であるからこそ、猫も犬もある程度言葉を理解する能力があるのだと思います。 そして私の専門分野である画像認識 AI でも、言語モデルと全く同じアーキテクチャを使って、画像を小さな 4x4 ピクセルのパッチに分解してそれを単語とみなして処理するだけで、非常に高い精度で画像認識ができるようになります。最近は画像も音声もテキストも扱えるマルチモーダルモデルが登場しています。非常に高い言語能力を獲得している最近の AI が使っているアーキテクチャは「言語専用」ではないんです。 認知言語学がまさにこの考え方で生成文法を批判的に捉えていたようです。❌️ 言語専用の特殊なルール(普遍文法)を、生まれつき脳に持っている ✅️ 言語専用の装置ではなく、物事の特徴を捉え、カテゴリー分けしたり、関連付けたりする「一般的な認知能力」を人間は生まれつき持っており、その応用で言葉も獲得するクラッシェンの仮説は当時の生成文法の影響を受けて「生得説」を前提としています。認知言語学は生成文法に疑問を投げかけていますが、「一般的な認知能力」は人間が生まれながらに持っているので、「生得説」を否定しているわけではありません。 では具体的に認知言語学がどのように言語学習に応用されているかというと、「コア・イメージによる理解」という考え方です。これが何かは説明するより見たほうが早いです。「イメージ」だけに。 私が最初に出会ったのは「新感覚☆キーワードで英会話」という NHK テレビ番組の本でした^62。講師の田中茂範先生は認知言語学の専門家だったんですね。 少し中身を引用させてもらいます。引用:「新感覚☆キーワードで英会話 2006年4月号」より take のコアイメージ上記はもう中古でしか手に入りませんが、最近は認知言語学に基づいたコア・イメージを取り入れた英語教材が増えてきています。例えば「英熟語図鑑」です。 @amazon(4761277033, https://m.media-amazon.com/images/I/81L4yreFRKL._SY425_.jpg)引用: 「英熟語図鑑」 より away のコアイメージ英語初級者向けに英語で書かれた文法書 Basic Grammar in Use にも、認知言語学の考え方が取り入れられています^63。 そうでないと初級者に英語で説明できないですよね。 @amazon(052113353X, https://m.media-amazon.com/images/I/41a3ceIROeL._SY425_.jpg)引用: 「Basic Grammar in Use" (Second Edition) より」「ネイティブがどう世界を捉えているか」というイメージの助けを借りることで、習得はもっとスムーズになる というわけです。 ただですね。コア・イメージを理解できたら、その組み合わせを理解できたり使えたりするか?というと、そうでないことが多いです。 分かるときもありますけど[^64]。 [^64]: 例えば多読を始めた時に絵本を呼んでいて、何かの実を摘む場面で "pick it off" という句動詞が出てきました。pick と off のコア・イメージを知っていたので、pick off という句動詞を知らなくてもそれをイメージで理解できました。 例えば車に乗る時は "get in the car" で、バスや電車に乗る時は "get on the bus/train" ですよね。 これはコア・イメージを理解することで感覚的に納得できますが、get と in, on のコア・イメージを知っているだけで言われて理解できるわけでも、自分で言えるわけでもないです。 むしろコア・イメージなんて知らなくても、そういう場面ではそれを使うものだと覚えて理解したり使えるようになれます。 私はコア・イメージを知ることが役に立っていると思っていますが、それはなぜそうなるのか?をある程度納得できる イメージしやすいので覚えやすくなる 句動詞を感覚的に理解できる 語彙同士の関連付けがしやすくなるという利点があるからです。 私はある語彙の意味を調べるときも、日本語と1対1で覚えるのではなく、色々書いてある日本語の意味から「コア・イメージは何?」を意識しています。英英辞典のような説明を日本語で理解してるのに近いかもしれません。 イマージョンラーニング イマージョンラーニングはクラッシェンの仮説に強く影響を受けています。しかしナチュラル・アプローチほど極端ではなく、初級者のうちは座学的な学習(単語、文法)も並行してやった方がよいと主張していますし、Anki などの SRS(忘却曲線に基づく単語の反復学習)も推奨しています。 クラッシェンの仮説に基づくナチュラル・アプローチやイマージョンラーニングは、最初は発話を求めない点で他と大きく異なります。 まずは大量の「理解可能なインプット」を受け取ることに集中し、発話は後から自然にできるようになるのを待ちます。 これは独学するには非常に都合が良い方法論です。レベルにあった大量のコンテンツさえあれば教師がいりません。 現代ではインターネットで大量の英語のコンテンツが手に入ります。 教師なしでアウトプットの練習をすると、修正してくれる人がいない(モニター役がいない)ので、直すのが難しい悪い癖がついてしまうリスクがあります^70。 しかしクラッシェンの仮説では、大量のほとんど理解できる i + 1 のインプットで言語の「獲得」が行われ、段々と自分で直感的に間違いに気付けるようになると考えます。これも非常に独学者に都合が良い考え方です。 問題は「本当だとしてめっちゃ時間かかって効率悪いのでは?」という疑問が浮かぶことです。 アウトプット仮説もあるので、 インプットだけで本当に十分なの? という疑問もあります。 アーミー・メソッドはなぜ成功したのか? オーディオリンガル・メソッドへの批判からコミュニカティブ・アプローチが生まれたわけですが、一方でアーミー・メソッドは成功した例^71として知られています。 その後のオーディオリンガル・メソッドと何が違ったのでしょうか? まず反復練習は退屈ですが、「戦時中であり、学習者が強制的に長時間集中して学習に取り組まざるを得なかった」というのは大きいと思います。 しかしそれだけであれば、熱心なオーディオリンガル・メソッドの学習者は大きな成果を得られるはずで、批判は「退屈で継続性に難あり」と言われるだけなはずです。 「言語科学者」と「ネイティブの協力者」の2協力体制言語科学者 学習者の母国語と同じ言語を話す ネイティブが出した素材を元に、体系化して学習に落とし込むネイティブの協力者 ターゲット言語のネイティブスピーカー 少人数のグループで、ひたすら実践的な会話や発音の模倣をリード母国語を完全に排除したわけではないようですね。 反復練習はネイティブの協力者が担当し、言語科学者は学習者の理解を助ける役割に徹していたようです。 とはいえ、クラス運営はほとんどターゲット言語偏重だったようです。 確かに説明よりもドリルによる反復練習に多くの時間を割いているのが特徴ではあるものの、学習者は「理屈での理解」があった上でそれを「身体的な訓練」で叩き込むという、かなり現実的な方法だったようです。 エリア・スタディーズ(地域研究) 言語学習と並行して、その地域の地理、歴史、政治、経済、文化、さらには人々の心理的背景までを徹底的に叩き込んだようです。 「国際共通語としての英語」とかじゃなく、特定の国や地域に実際に行って活動することを想定していたのですから当然です。 言語と文化は深く結びついています。言葉の背景にある「意味」や「文脈」が補完され、習得が加速するはずです。 圧倒的な時間と環境学習時間 出典により幅があって正確な時間はわからないものの、1日10時間、週6日という情報もあり、かなりの集中学習だった模様 そのうち週15時間がネイティブスピーカーとのドリル、20〜30時間が自習? 6週間のコースを2〜3回少人数制 少人数グループで、ネイティブと濃密に接触。実践的な教材 専門の教師ではないネイティブが「モデル」だったため、彼らの日常的な言い回しや、状況に応じた微妙な変化が自然に混じった。 単なるリピートだけでなく、ネイティブが投げかける質問に対して即座に応答する「やり取り」の要素が強い。 練習するフレーズが極めて具体的な「任務」と直結していた(軍事任務遂行や現地での生活に即した内容)。 実際に任務で使う状況に即したスキット(寸劇)やロールプレイ的活動も行われた。戦時という強烈な動機づけと「落第=任務に支障」というプレッシャー考察 アーミー・メソッドの特殊条件付きの成功を、(当時の学説^80をベースに)一般教育に適用しようとして、より「ドリル中心」「目標言語のみ使用」「文法説明は極力排除」という色合いが強まってしまったという感じでしょうか? オーダーメイドの学習プログラムを、画一的な量産型の学習プログラムに変えてしまった上に(それは仕方ないとして)、当時の学説^80を過度に取り入れてしまったように思います。手法 文法 内容 目的ASTP 言語学者が論理的に解説 文化・歴史・政治を同時に学ぶ実践的な内容 生死に関わる任務遂行ALM 規則を教えず、ドリルから「察せ」 文脈のない、無味乾燥な例文の繰り返し テストや一般的な教養(ASTP=アーミー・メソッド、ALM=オーディオリンガル・メソッド) 圧倒的な学習時間と動機は最重要だったとして、文法の説明による理解を完全には排除しない ネイティブスピーカーとのやり取りを重視する 実践的な内容を扱う 文化・歴史・政治など、言語の背後にある文化も学ぶ辺りが、オーディオリンガル・メソッドに対して取り入れるべきポイントではないかと思います。 特に後者2つは知的好奇心を刺激し、情意フィルター仮説の観点からも重要でしょう。 一方でパターンプラクティス自体は非常に有効だったのではないでしょうか。文法だけを重視した無味乾燥な文の機械的な反復ではなく、できれば自分事化して、実際に使う場面を想定した内容で練習できるのが理想です。[^85] [^85]: 去年からそういうパターンプラクティス教材を AI に作らせるアプリを開発したいと考えています。どう実現するかの大まかな構想は既にあります。今作っているセンテンス・マイニングを AI にやらせるアプリをリリースしたら、次に作り始めるかもしれません。 なおオーディオリンガル・メソッドの1つである DME メソッドは比較的新しく、カラン・メソッドを踏襲してドリル中心であるものの、文法説明があったりロールプレイ要素を足すなど幾らか改良が加えられているようです。 何気に非常に重要だと思う点があります。軍人たちは短期集中訓練の後に現地に赴任したわけです。 つまり イマージョンの前の準備として機能した とも考えられます。 特に「半年後に海外赴任が決まった」というような「短期で準備しなければならない」状況では、かなり参考になるのではないでしょうか? 「科学的な第二言語習得理論」という言葉の罠 最近よく「科学的な第二言語習得理論に基づいた学習法」という言葉を見かけます。 私の意見では第二言語習得理論は、一般の人が思う自然科学のような強い意味での「科学的」とは同一視しにくいです。 例えばクラッシェンの仮説は、実験的に証明された理論ではなく、観察に基づく仮説です。 観察結果を論理的に説明した「理論」ではありますが、実験的に証明されていなければ「仮説」です。クラッシェン自身も「仮説」と呼んでいます。 観察に基づいているので、ある程度の信憑性はありますし、仮説が提唱されてから色々な研究はされているでしょうが、厳密に自然科学のような手法で証明するのは困難です。 そもそも科学的であるものは基本的に反証可能であり、後の研究で反証されてひっくり返る可能性があります。 ましてや「仮説」では、まず間違いなく正しいだろうと信じるようなものではないはずです。 第二言語習得理論 (SLA) は言語学、心理学、教育学、社会学が絡み合う領域です。 SLA では、自然科学のような厳密な実験的手法で証明するのは難しいと考え、観察や統計、反証可能性^89を重視するアプローチが取られています。要素 SLAにおける役割観察 実際の教室や会話データから、どのようなエラーが起きるか、どう発達するかを記録する。統計 偶然起きた現象なのか、それとも意味のある差(有意差)なのかを数学的に判断する。反証可能性 「〇〇という条件なら、△△という現象が起きる」という仮説を立て、それが間違っていることを証明できる余地を残す。「科学的な第二言語習得理論に基づいた学習法」は、意図的に自然科学の厳密な「科学」を想起させて「正しい」と思わせる、キャッチフレーズの側面が強いのでは?と感じています。 悪だと断罪してるわけではありません。社会学も心理学も現代では科学的手法を取り入れており、科学の一分野として発展しています。誰かの経験や勘に基づいた方法ではなく、「観察や統計に基づいた根拠がある」というのは正しいです。ただ購買者の認知バイアス(先入観や思い込み)を利用したマーケティング手法という側面がありそうです。 私も第二言語習得理論はとても参考になると考えています。しかし「まず間違いなく正しい」とまでは信じていないので、組み合わせてリスクヘッジしようと思っています。 オーディオリンガル・メソッドも過度に当時の学説^80を取り入れて極端になってしまった面があるようですし、第二言語習得理論を過度に信じすぎるのはリスクがあると思います。 日本人にとっての英語という言語の特殊性 ここまで紹介した教授法や独学法は、特に日本人が英語を学ぶ場合に限らない一般論です。 しかし日本人にとって英語は非常に遠くて難しい言語です。 文法も文字体系も音も考え方も何もかもが遠いですが、特に難関なのは「音の違い」 です。 英語の文法はヨーロッパ系の言語の中ではかなり単純です。 文字体系はアルファベットで単語は空白区切りと、非常にシンプルです。逆に英語圏の人が日本語を学ぶ場合を想像してみてください。音よりも文字体系の方が遥かに難しいでしょう。 音が難関だという点を踏まえると、クラッシェンのインプット仮説は、日本人が英語を学ぶ場合には「それだけでは危うい」のではないかと考えています。 つまり口に出す訓練をしないと、聞き取る力さえも身につくのが相当遅くなる可能性があります。これは多くの日本人英語学習者の経験でも聞く話です。 インプットだけでそのうち自然に言語を獲得して話せるようになるかはもっと怪しいです。 カナダの英語とフランス語という近い言語のイマージョン教育でさえ、インプットだけではスピーキングに課題があるという話もありますし、日本人が英語を学ぶ場合には発音や音の変化を学んで口に出す練習は必須ではないかと考えています。 ただし最初はインプットから入って、ある程度英語の音に耳が慣れてから口に出す練習を始めるのが良いと思います。そして徐々にアウトプットの比率を増やしていくのが良さそうです[^95]。 [^95]: 私は15年ほど前に、英語上達完全マップ を参考に、(先に発音や音の変化を学んでから)中学3年分の教科書を1つの文を音声とピッタリ一致するようにスラスラ言えるまで何百回も徹底的に真似して音読するということをやりました。最終的に暗記するつもりはなかったのに風呂場で空で言えるようになってました。効果は抜群だったものの、順番は先にインプットをある程度やった後のほうが良かったと思っています。 急進的な考え方は避けたほうが良さそう まず過去の歴史を振り返ると・・・ダイレクト・メソッド以降の「話せるようになりたいなら、母語の影響をできるだけ減らしてターゲット言語に浸かるのが良い」という方向性は維持されています。 しかし母語の利用や、文法や単語を学ぶ「学習」を完全に排除する急進的な方法論は、必ずしも良い結果を生まないように思います。アーミー・メソッドとその後のオーディオリンガル・メソッドとの違い クラッシェンもモニターとしての一定の「学習」の効果を認めている クラッシェンの仮説をベースにしているイマージョンラーニングも、初級者のうちは基礎単語や文法を学ぶことを並行してやるように勧めている 文法を重視しないコミュニカティブ・アプローチには、正確な言語運用能力が身につかないという批判がある現代の第二言語習得理論では「フォーカス・オン・フォーム」(意味のあるやり取りの中で、必要に応じて文法に注意を向ける)[^96]が推奨されているようです。 [^96]: 提唱者のマイケル・ロング(Michael Long)はインタラクション仮説の提唱者でもあります。基本的にはコミュニカティブ・アプローチ的な「意味のあるやり取り」を重視しています。必要なときにだけ文法に注意を向けます。 ややこしいのですが、オーディオリンガル・メソッドのような形式重視の教授法は「フォーカス・オン・フォームズ」(Forms が複数形)と呼ばれ、コミュニカティブ・アプローチの意味重視は「フォーカス・オン・ミーニング」と呼ばれます。バランスを取って、意味のあるやり取りの中で、必要に応じて文法に注意を向けるのが「フォーカス・オン・フォーム」です。 最近の研究はどうなってるの? ここまでに紹介したものは基本的に20世紀のものなんですよね・・・。イマージョンラーニングは最近の独学法ですが、理論的な背景は主にクラッシェンの仮説です。 私は第二言語習得理論の専門家ではないので、21世紀の最新研究動向がどうなっているか詳しくは分かりません。 クラッシェンの仮説に対して現代の研究者からは「定義が曖昧で反証可能性が低い」と批判されることがあるようですし、より科学的アプローチが取られていそうです。 これについては、別途調査したらまた記事を書こうと思います。 それぞれをどう組み合わせるのか? イマージョンラーニング テスト対策ではなく実践的な英語を独学するのなら、これを中心に据えるのが良いと考えています。 ここで紹介してきた他のものは、全て教師がいる前提の教授法です(パターンプラクティスは独学用の教材があります)。 「オンライン英会話やっています」「英会話教室通っています」という「教師を付けている人」も、大量のインプットはやった方がよいでしょう。 個人的にイマージョンラーニングの「大量のインプットが重要だ」はそうだと思うけれど、ちょっと極端すぎると思っています。 イマージョンラーニングだけにオール・ベットするほど信用していないです。 文法訳読法 これは学生時代にやったので、基本的にはもういいでしょう。 何に役に立ったかと言えば、英語を読む時に、英語を英語のまま理解するのが難しい箇所でも、そこだけ頭で日本語にしながら理解できる クラッシェンの言うところの「i + 1」の i の部分がちょっと厳しい状態でもイマージョンラーニングを進められることです。 聞いて分からない → 字幕を見る → 分からない単語や表現を調べる → 文法的にどういう構造か理解する → 文の意味が分かる これで文の意味が分かれば、それを繰り返し聞けば、意味が分かっている音声を何度も聞いて瞬間理解まで定着させることができます。 また文法をパターンプラクティスで自動化するまで叩き込むとしても、先に理解できていた方が納得しながら練習できると思います。 ダイレクト・メソッド / アーミー・メソッド / オーディオリンガル・メソッド イマージョンラーニングでは、アウトプットを全然重視していないので、そこを補完できると考えています。 アウトプット仮説によると、アウトプットすることがインプットの質も高めるそうですし、インプットとの相乗効果が見込めて効率が上がりそうです。 そもそも軍事目的の短期集中による時間効率を重視して成功した手法です。イマージョンラーニングの「時間がかかる」という欠点を補完できるかもしれません。 機械的な反復練習だけではダメという点を踏まえ、アーミー・メソッドとその後のオーディオリンガル・メソッドとの違いも意識したうえで、以下を取り入れられそうです。できるだけ日本語を介さずに、英語をイメージと直接結びつける とはいえ、日本語による理解を完全には排除しない 文字よりも音を重視する 発音やリズム、イントネーションを真似して口に出す練習をする パターンプラクティスで文法や表現を反復練習で自動化する パターンプラクティスで行う反復練習内容について、文法の説明による理解を排除しない 「英語のハノン」には簡単ではあるが文法解説があるパターンプラクティスで言葉を発する際に、その文を自分の意図として伝えるつもりで言う 文化・歴史・政治など、言語の背後にあるものも学ぶ日本語と英語は音が違いすぎるので、真似して口に出す練習はリスニングに極めて有効です。真似して口に出す練習をしておかないと、言いたいことを英語にできても言えないです。 そしてパターンプラクティスには数は少ないものの独学用の教材(「英語のハノン」と「パタプラ イングリッシュ」)があります。 とはいえ教材が少ないですし、どちらも中級・上級向けです(「英語のハノン 初級」でさえ)。リピーティングやシャドーイング、リード&ルックアップなどの他の練習も取り入れることになるでしょう。 認知言語学(コア・イメージ) コア・イメージを理解することは、イマージョンラーニングの助けになると思います。 根っこが生得説に繋がるので、相性が良さそうです。語彙のコア・イメージを理解しておくどうしても年齢が上がるほど母語の影響が強く、大人になれば大量のインプットだけで自然に言語を獲得するのは難しくなります。 ネイティブの感覚をコア・イメージで理解することで、大量のインプットを通してそれを自然に吸収しやすくなるのではないかと考えています。 あくまでイメージと結びつけるのですが、コア・イメージを理解するのに日本語の助けを借りるのは構わないと思います。母語による理解の助けが得られるのは大人の強みです。 コミュニカティブ・アプローチ 独学が基本の自分が取り入れられる要素としては、英語を道具として使う意識を持つ 実際に使う場面を想定して、自分の意図を伝えるつもりで練習することでしょうか。 そのうち英会話を始めることも考えると思いますが、現時点ではまだ独学で良いと考えています。 インタラクション仮説は気になりますが・・・。 まとめ 前提:動機(目的)をはっきりさせておく 時間をかけることは必要方法として取り入れること:独学に向くイマージョンラーニングを中心に据え、実践的で意味のある大量のインプットを受け取る 文法訳読法はもう十分なので、必要に応じて文法的知識を補う できるだけ日本語を介さずに、英語をイメージと直接結びつける とはいえ、日本語による理解を完全には排除しない 語彙のコア・イメージを理解しておく 文字よりも音を重視する ネイティブの発音やリズム、イントネーションを真似して口に出す練習をする イマージョンラーニングで出会った「状況における意味のある内容」を、自分の「意図」として伝えるつもりで真似する パターンプラクティスを取り入れて文法や表現を反復練習で自動化する パターンプラクティスで行う反復練習内容について、説明による理解を排除しない パターンプラクティスで言葉を発する際に、その文を自分の「意図」として伝えるつもりで言う 英語を道具として使う意識を持つ 文化・歴史・政治など、言語の背後にあるものも学ぶ^100
2025年の英語学習の振り返り
2025年はイマージョンラーニングを中心にして英語学習を再開しました。1年を振り返ってみます。 目次 最初の半年 最初の頃は、以下をやっていました。Netflix で英語 CC 字幕付きの日本アニメの英語吹き替え版を観る 分からない単語や表現を Anki にセンテンス単位で登録して覚える 受験単語レベルでも抜けがあると感じていたので、シス単の Anki デッキ[^1] を使って覚える 文法のおさらいを兼ねて「英語のハノン 初級」を1周やってみた 中学レベルのシャドーイングを Duo Basic でやってみた[^1]: 買った書籍(5訂版)とバージョンが違ってました。それは諦めるとしても、間違っている箇所を直したり、対応する日本語だけでは不十分で辞書を引いて追記したりで、なんだかんだデッキの修正と改善にかなり時間をかけてしまいました。後から知ったのですが Ankiカード作成屋【Ankiデッキ】システム英単語 を買えばよかったです。 これを始めた時点では「イマージョンラーニング」という言葉は知らなかったのですが、最初の2つは後で調べてみたらまさにそれでした。 Refold のサイトはしっかり読みました。 自分の経験で多読が「英語を英語のまま理解する」のに効果的だったので、「日常会話を聞き取れるようになる」という目的に対して、英語の多聴・多観を取り入れた形です。 ただし多読では単語を覚えられなかったので、Anki で単語や表現を覚えるのを組み合わせました。 学習方法の見直し モチベーションが高いうちに色々試してみたというところですが、半年ほどやったところで風邪を引いて Anki が止まったのをきっかけに、そのまま再開するのではなく見直すことにしました。センテンス・マイニングは苦じゃなかったけど、辞書やらネットで調べる時間は無駄にしている感じがした シス単で抜けてた単語も概ね覚えた 単語や表現は覚えていくけど、リスニングが伸びている感じはあまりしない 瞬間英作文が好きでない自分には、「英語のハノン」のパターンプラクティスは合ってたけれど、アウトプットより先にインプットをやりたい(1周は終わらせた) 仕事でも日常でも英語を使うわけではないので、そこまで英語の優先順位高くない・・・(今さら?)ではどうするか?Netflix で英語 CC 字幕付きの日本アニメの英語吹き替え版を観る(継続) Anki はやめる センテンス・マイニングを AI にやらせるアプリを作る Passive Listening を積極的に取り入れる(Anki をやめても繰り返し聞くので覚える)アプリを作る時間を捻出するために、英語の学習時間を減らすという本末転倒な暴挙に出ました。 ちょうど 2025 年はソフトウェア開発に AI エージェントを使うのが当たり前になった年です。2024 年末から正月にかけて試して「これはスゲー、取り入れないと置いていかれるわ」と感じたものです。 一方で仕事での AI 開発ではアプリ開発ほどコードを書かないので、個人開発で AI エージェントを使ったアプリ開発の経験を積みたかったのです。 後半半年の学習方法 2025年後半の学習方法は以下のループを回すシンプルなものになりました。Free-flow Immersion 字幕無しで見て、意味は調べない わかる範囲で楽しむ 話が分からなくなりそうなら、英語字幕や日本語字幕を確認して楽しめるようにするIntensive Immersion 分からない単語や表現が含まれる文を、アプリで AI に単語や連語・表現を抽出させ、その中から選択するPassive Listening トイレ、歯磨き、コーヒー淹れるなどの隙間時間に繰り返し聞く最初は楽しんで見て、次にちゃんと理解して、最後に繰り返し聞いて定着させる、という流れです。 2 がどんな感じなのか分からないかと思います。こんな感じで指定した文から単語や表現を抽出してくれます。その中から「ああ、これこれ」というのがあったら、チェックを入れて保存します。 センテンス・マイニングが楽になったことと、Anki で覚えることをしなくなったため、センテンス・マイニングの数を絞ることはやめました。 結局、積極的に行う英語学習時間 (Active Immersion = Free-flow / Intensive) は1日30分〜1時間半くらいに減りました。 実際にはアプリ開発を優先したり体調不良でサボった日もあるし、12月は仕事が忙しくて完全に停止したので、平均すると1日40分くらいでしょうか。 しかし隙間時間に行う Passive Listening で1時間半ちょっと「意味が分かる」リスニングの時間を確保したことで、リスニング力はかなり伸びた実感があります。 もっと早くに Passive Listening を取り入れればよかったです。 体調崩してたり仕事が忙しかったりで Active Immersion (Free-flow / Intensive) ができない時でも、Passive Listening だけは続けるようにしています。 おかげで、平均1時間半くらい英語を聞くことを毎日継続できています。 Passive Listening は、直近に見たものを繰り返し聞きつつ、過去に見たものも定期的に聞くようにしています。 自力で忘却曲線に逆らうようにしているわけです。 英語の学習時間が取れていない期間もあるので、そういう時に聞ける過去のストックがあるのは助かります。 後半の後半で効果があった方法 9月くらいから Passive Listening 中にサイレント・シャドーイング(声に出さず頭の中でシャドーイング)するようにしました。 話の内容は分かっているものを聞いているため、どうも英語の細かいところがあやふやなままでも「聞き取れてるつもり」になっている感じがしたからです。 Duo Basic くらいならともかく、学習用に手加減されていないものをシャドーイングできるほどの実力はないですし、自分の声で音声が聞こえなくなるのが嫌なので、サイレント・シャドーイングです。 これは確実に効果があると感じました。 しかしサイレントだと声に出す練習になりません。 そこで10月くらいからは Intensive Immersion の時に、声に出してシャドーイング / リピーティングするようにしました。 凄く簡単なものはシャドーイング、それ以外はリピーティングです。 幾らかは Read and Look up(文を一旦頭に入れてから見ないで真似する)をやります。 とはいえ、目的は Intensive Immersion なので、あまり時間がかかり過ぎない範囲でやっていました。 というか、マジでやってたら時間かかりすぎて「これは続かないわ」と思って手を抜くようにしました。 これも物凄く効果がありました。 その後の Passive Listening で明らかに良く聞き取れるようになります。 12月は仕事が忙しくて Active Immersion は停止しましたが、Passive Listening で気が向けば小声でシャドーイングするようになっていました。出来るところだけですが。 成果 前半 前半半年は既に記載した通り、あまりリスニングが伸びている感じがしませんでした。 Anki 利用で語彙力が増えたことと、文法の復習ができたことが良かったです。 後半に向けての土台作りにはなりました。 瞬間英作文が好きでない自分には、「英語のハノン」のパターンプラクティスが向いていると分かったのは収穫でした。 しかしたった1周では効果は感じられていません。 アウトプットの効果を感じるような英会話もしてないですし。 後半 後半半年は Passive Listening を取り入れたことで、リスニング力が大きく伸びたと感じています。 語彙力も単語はあまり増えていませんが、句動詞、イディオム、表現などの語彙は増えましたし、既に知っているものも瞬間理解できるものが増えました。 英語のハノン、リピーティングをやったことで、言おうと思った英文をつっかえずにスラスラ言えるようになってきた感じもあります。 もちろん、まだ「言いたいことを言う」本来のアウトプットの練習はしていないので、実際に会話できるかは別問題ですが。 つまり、言いたいことを英語にできる それを口に出して言えるのうち、2 はかなり改善しました。 そして 2 が改善すると、リスニング力が上がります。人は自分が発音できる音は聞き取れるようになるからです。 学習効率 考えてみれば前半の方が「英語のための学習時間」は多く取っていました。2時間〜3時間/日くらい。 Intensive Immersion にも時間をかけていましたし、Anki に30〜40分くらい、英語のハノンも15〜20分程度毎日やっていました。 しかし「英語のための学習時間」を30分〜1時間半(やらない日もあったので平均40分くらい?)に減らした後半の方が、リスニング力は大きく伸びました。 実際には Passive Listening に1時間半以上かけているので、1日平均2時間以上は英語を聞いていたことになります。 Passive Listening は隙間時間にやっているものの、結構集中して聞いていました。「ながら聞き」ですが、英語のリスニングの方に意識が向いていて、ながらでやっていることの方には頭や意識を使っていない感じです。 確実に後半のほうが効率的に学習できたと言えます。 最後に開発中のアプリについて 自分のために作ったアプリですが、自分が使う分には十分実用的になりました。 公開しようと考えているのですが、人に使ってもらうとなるとまだもう少し開発に時間がかかりそうです。 自分用なら自力でデータ復旧できるのですが、人に使ってもらうとなると「ごめんなさい、前のデータはもう使えないので捨てるか、何とか自力でデータ移行してください」「ごめんなさい、バグがあってデータ壊しちゃいました」とはいきません。 春くらいには公開するつもりで開発を続けています。
Nx で Firebase Functions を扱う方法
目次 Nx って何? Nx は Angular や React を使った企業での Web 開発で利用されることを想定した monorepo ツールです。 公式では Angluar と React をサポートしています。React が使われている静的サイトジェネレータの Gatsby も。 サーバーサイドでは Express, Nest.js, Next.js がサポートされています。 プラグイン機構になっており、他にもサードパーティが提供するプラグインで色々サポートすることができる仕組みになっています。 Vue.js のプラグイン もあります。同じところが Nuxt.js のプラグイン も提供しています。 あるシステムを開発するとして、ユーザー用 Web アプリ 管理者用 Web アプリ バックエンドのサーバーアプリといった具合に幾つかのプロジェクトで構成されていることが多いかと思います。それぞれリポジトリ分けてると面倒だなぁ このデータ型定義、あっちのアプリでもこっちのアプリでも同じの使ってるなぁ、1箇所にまとめたいなぁ ユーザー用と管理者用でどっちでも利用する画面コンポーネントがあるんだけどなぁ、1箇所にまとめたいなぁ ESLint の設定やら TypeScript の設定やら、同じような設定をあっちにもこっちにも、共通定義を拡張する形にできないかなぁといった、「共通化したい」要求が出てきます。 それを叶えるために monorepo 構成にするわけですが、Nx は 特に Angular や React などの Web アプリ開発に特化した monorepo 管理ツールになります[^1]。 [^1]: 他に有名な monorepo ツールに Lerna があります。Nx のブログに Why you should switch from Lerna to Nx という記事があります。オープンソースでライブラリ開発なら Lerna だけど、企業でアプリ開発するなら Nx だという主張で理由が述べられています。 英語ですが動画付きでチュートリアルもあります。どんな感じのものか掴めるかと思います。 全ての npm パッケージはプロジェクトルートの node_modules にインストールされ、一括管理されます。 動機 勤め先の creato(クリート) では React も Vue.js も利用していますが、第一選択肢は Angular です。 Nx は親和性が高く利用したいのですが、Nest.js より Firebase を使うプロジェクトの方が多いです。 ところが Nx は Firebase(というか Functions)にデフォルトでは対応していません。 Nx で Firebase Functions を扱う方法が Allow development of Firebase Cloud Functions で議論されています。 実際に試してみたので、ここで日本語で手順をまとめて共有したいと思います。 前提 Node.js が動作する環境を用意してください。 Git も必要です。 Unix コマンドを使います。Linux や macOS は良いですが、Windows でコマンドプロンプトや PowerShell を使う場合は、記載されているコマンドで動作しないものがある(rm など)ため、各環境でのコマンドに置き換えてください。 実際の作業は Docker コンテナを作ってやったのですが、ここでは理解を簡単にするために Docker を使わない前提で説明します。 前提知識や用意するものを増やしたくないので。 yarn は利用せずに Node.js 付属の npm を利用して説明します。 自分が試した環境は以下です。Node.js 14.16.0 Nx 11.4.0 および 11.6.1Nx ワークスペース作成 git の user.email と user.name は設定しておいてください。 ワークスペース作成の最後で email と name が取得できずにエラーが発生します。 Nx の monorepo ワークスペースを作成します。 npx create-nx-workspace@latestどのタイプのワークスペースを作成するか聞かれます。 ここでは Functions 以外に何を利用するかは特に想定していないので empty を選択しておきます。 "Use Nx Cloud?" は "NO" で。 empty を選択しておいて後から Angular でも React でも、必要なアプリケーションは追加可能です。 empty 以外を選択した場合はあくまでお手軽セットアップができるだけです[^2]。 [^2]: Angular を試してみましたが、empty にしておいて後から追加した方がオプションで指定できるものが多いので好ましいかもと思いました。 ワークスペース名はリポジトリ名となるものを指定すれば良いかと。 > NX NOTE Nx CLI is not installed globally. This means that you might have to use "yarn nx" or "npx nx" to execute commands in the workspace. Run "yarn global add nx" or "npm install -g nx" to be able to execute command directly.と表示されます。 もし nx コマンドの実行時に npx nx と打つのが面倒なら nx パッケージをグローバルインストールしてください。 npm i -g nxグローバルを汚したくないなら[^3]、npx nx でローカル node_modules 内の nx を実行できるので必須ではないです。 ここではコマンドを簡略化して記載するために、グローバルインストールされている前提で説明します。 グローバルインストールしない場合は、nx の前に npx を付けて実行してください。 [^3]: 複数の Nx ワークスペースがある場合、ワークスペースごとに利用している Nx のバージョンが異なることは容易に考えられます。グローバルとローカルの nx バージョンが合わないことで要らないトラブルを招くより、ローカルの nx を実行した方が良さそうに思います。自分は Docker で環境を分離するので、Docker コンテナ内でグローバルインストールしますが。 Firebase プロジェクト作成 先に Firebase コンソールでプロジェクトを作成しておきます。 Firestore を利用するなら、利用開始をしておきます(リージョンは後から変更できないので注意。asia-northeast1 が東京、asia-northeast2 が大阪。asia-northeast3 はソウル、日本ではないので注意。) firebase-tools をグローバルインストールしておきます。 npm i -g firebase-toolsFirebase にログインして、プロジェクトの初期化を行います。 firebase login firebase initここでは Functions を利用するように答える想定で説明します。 他は利用したいものを指定してください。 Functions は TypeScript 利用、ESLint 利用を選択。 install dependencies は n を選択 (後で Nx ワークスペース機能を利用するので、ここでインストールする必要がないためです)。 .gitignore 最初から .gitignore が存在していることで、firebase init した際に .gitignore が生成されません。 普通に作成した際に生成される .gitignore の内容で、Nx が生成した .gitignore に足らなくて必要そうな firebase 関係のものを追加しておきましょう。 また firebase ディレクトリも .gitignore に追加しておきます。 この中にユーザー認証情報やエミュレータの実行ファイルなどが格納されます。 これらは git 管理しなくても、他の開発者が firebase login や firebase emulators:start した際に、必要なものが作成されるようです(全部 ignore して良いか自信ないので、firebase ディレクトリ以下で必要なものがあるなら、もう少し細かく指定してください)。 以下を .gitignore に追加しておきます。 # # Firebase #firebase# logs firebase-debug.log* firebase-debug.*.log*# Firebase cache .firebase/Node アプリケーション追加 Functions アプリケーションを Node.js アプリケーションとして追加します。 コンソールで以下を実行します。 npm i -D @nrwl/node nx g @nrwl/node:application functions最初の npm コマンドは Node アプリケーションの作成機能を Nx に追加します。 このように作成できるアプリケーションの種類をプラグインとして追加していく仕組みになっています。 (Nx 11.4.0 の時点では nx add @nrwl/node で追加しましたが、11.6.1 では nx add コマンドは削除されていました。) 次の nx コマンドで Node アプリケーションを追加しています(nx g は nx generate の省略形)。 apps/functions が生成されます。 (アプリケーションは apps の下に、ライブラリは libs の下に配置されます) ESLint の tsconfig や ESLint の設定は、親にある共通設定が利用されるようになっています。 Functions でだけ適用したいものがあれば追加できるけれど、ここではこのままで。 プログラムの中身は firebase init が生成するものもほぼ空っぽだけれど、一応入れ替えておきます。 ここに Functions の関数を実装していくことになります。 cp functions/src/index.ts apps/functions/src/main.tsFirebase 関係のパッケージ追加 firebase init が生成した package.json に含まれているパッケージのうち、足らないものを Nx ワークスペースにインストールします。 npm i firebase-admin firebase-functions npm i -D firebase-functions-testデプロイ用 package.json 生成ツール追加 tools/scripts/build-firebase-functions-package-json.ts を作成します。内容は以下です。 途中にある engines: {node: '14' } のところは、Firebase Functions で利用する Node バージョンに置き換えてください。 この記事執筆時点(2021/04)では 14 はまだ Public Preview です。 import * as depcheck from 'depcheck'; import * as fs from 'fs'; import * as path from 'path';import * as packageJson from '../../package.json';const PACKAGE_JSON_TEMPLATE = { engines: { node: '14' }, main: 'main.js', };async function main(): Promise<void> { const args = process.argv.slice(2); if (!args?.length || !args[0]) { throw new Error('Application name must be provided.'); } const APPLICATION_NAME = args[0]; console.log(`Application name: ${APPLICATION_NAME}`); /***************************************************************************** * package.json * - Filter unused dependencies. * - Write custom package.json to the dist directory. ****************************************************************************/ const ROOT_PATH = path.resolve(__dirname + '/../..'); const DIST_PROJECT_PATH = `${ROOT_PATH}/dist/apps/${APPLICATION_NAME}`; console.log('Creating cloud functions package.json file...'); // Get unused dependencies const { dependencies: unusedDependencies } = await depcheck(DIST_PROJECT_PATH, { package: { dependencies: packageJson.dependencies, }, }); // Filter dependencies const requiredDependencies = Object.entries(packageJson.dependencies as { [key: string]: string }) ?.filter(([key, _value]) => !unusedDependencies?.includes(key)) ?.reduce<{ [key: string]: string }>((previousValue, [key, value]) => { previousValue[key] = value; return previousValue; }, {}); console.log(`Unused dependencies count: ${unusedDependencies?.length}`); console.log(`Required dependencies count: ${Object.values(requiredDependencies)?.length}`); // Write custom package.json to the dist directory await fs.promises.mkdir(path.dirname(DIST_PROJECT_PATH), { recursive: true }); await fs.promises.writeFile( `${DIST_PROJECT_PATH}/package.json`, JSON.stringify( { ...PACKAGE_JSON_TEMPLATE, dependencies: requiredDependencies, }, undefined, 2 ) ); console.log(`Written successfully: ${DIST_PROJECT_PATH}/package.json`); }main() .then(() => { // Nothing to do }) .catch(error => { console.error(error); });要するに、ワークスペースルートにある package.json を元に、Functions のソースコードが依存していないものを除外する Node.js ランタイムのバージョン指定を追加したデプロイ用の package.json を生成するスクリプト。 依存関係を調べるのに depcheck パッケージを利用するのでインストール。 npm i -D depcheckこのスクリプトのディレクトリに tsconfig や ESLint 関係のファイルを追加しておきます。 ルートにあるものを参照するだけでいいので、apps/functions と同じでいいです(ちょうど階層的にも ../.. がルート)。 一応 Jest でテストも作成可能なように jest.config.js もコピーしておきます。 cp apps/functions/*.json tools/scripts cp apps/functions/.eslintrc.json tools/scripts cp apps/functions/jest.config.js tools/scriptsただ、このスクリプトでは json ファイルを import していて、このままだと resolveJsonModule オプションを付けることを考えろというエラーが出ます。 また Node.js は import を使うとエラーになる[^4]ので、TypeScript から JavaScript へ変換後の形式は commonjs にしておきます。 tools/scripts/tsconfig.json に以下を追加。 [^4]: 一応 Node 12 から ES Modules はサポートされていますが、実験的機能のため --experimental-modules オプションを指定する必要があったり、拡張子を mjs にする必要があったり。TypeScript が commonjs にトランスパイルしてくれるのに、それらを使う意味がありません。 { // 略 "compilerOptions": { "resolveJsonModule": true, "module": "commonjs" },なおツールの実行時には ts-node に --project ./tools/scripts/tsconfig.json オプションを指定し、利用する tsconfig を明示しないと自動認識はしてくれませんでした。 workspace.json 編集 Nx ワークスペース作成時に empty を指定してれば、プロジェクトルートに workspace.json があるはずです。 なんでこんなことを書いているかというと、angular を選択したときは angular.json という名前で作成されたからです。 workspace.json(または angular.json)を編集して、build, serve, deploy などのコマンド実行時の処理を変更、追加します。 Nx で Node アプリケーションとして追加した時点で、workspace.json にアプリ設定が追加されていますが、それをさらに編集します。 コメントは説明のために記載しているもので、実際のファイルには記載しません。 "functions": { // 略 "architect": { // build の項目を build-node に変更。 // build 時に他にも実行したことがあるので、サブコマンド扱いにする。 "build-node": { // 略 }, // build の項目を新しく追加 // build-node を実行して TypeScript ビルドした上で、package.json の用意も行う。 "build": { "builder": "@nrwl/workspace:run-commands", "options": { "commands": [ { "command": "nx run functions:build-node" }, { "command": "ts-node --project ./tools/scripts/tsconfig.json tools/scripts/build-firebase-functions-package-json.ts functions" }, { "command": "cd dist/apps/functions && npm install --package-lock-only" } ], "parallel": false }, "configurations": { "production": { "prod": true } } }, // ビルドした上で、Firebase Emulator を使って実行するように書き換え "serve": { "builder": "@nrwl/workspace:run-commands", "options": { "command": "nx run functions:build && firebase emulators:start --only functions --inspect-functions" } }, // ビルドした上で functions:shell を利用できる設定を追加 "shell": { "builder": "@nrwl/workspace:run-commands", "options": { "command": "nx run functions:build && firebase functions:shell --inspect-functions" } }, // shell と同じ "start": { "builder": "@nrwl/workspace:run-commands", "options": { "command": "nx run functions:shell" } }, // デプロイ設定を追加 "deploy": { "builder": "@nrwl/workspace:run-commands", "options": { "command": "firebase deploy --only functions" } }, // lint と test はそのまま // 略 } }大事なのはビルド設定だけです。 あとはビルドと firebase コマンドの組み合わせだったり、firebase コマンドを実行するだけだったり。 それらを追加する必要性を感じないけれど、firebase init が生成した package.json の scripts にあるものを移植した感じです。 いらなくなった functions ディレクトリ削除 参考にするために残していましたが、もう移植し終わったので firebase init が生成した functions ディレクトリを削除します。 rm -rf functionsデプロイ設定 Functions のデプロイ時のビルド設定を行います。 ソースコードの場所の階層がプロジェクトルートに対して ./functions から ./apps/functions に変わっています。 このままだとデプロイ時にエラーが発生するので、明示的に source を指定する必要があります[^5]。 [^5]: Error: An unexpected error has occurred. と表示され、firebase-debug.log を見ると TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined というエラーが出てました。エラーが分かりにくいけど、そういう理由でした。 firebase.json を編集します。 "functions": { "source": "dist/apps/functions", "predeploy": [ "nx lint functions", "nx build functions" ] },なお nx コマンドがサブコマンドとして認識できる lint や build, test, serve, e2e などに関しては、nx run functions:lint を nx lint functions のように書くことができます。 まとめ Angular や React を使った企業での Web 開発で利用されることを想定した monorepo ツールである Nx で、残念ながら企業での Angular や React を使った Web 開発でよく利用される Firebase に対応したプラグインが提供されていなかったため[^6]、手作業で対応する方法を解説しました。 [^6]: 正確には Google Cloud Functions Generator が見つかったのですが、Functions の関数1つにつき1つのアプリ扱いされるもので、およそ企業での Web 開発に向くものではありませんでした。 元々は GitHub の Issue で議論されていた内容 です。やっている内容をプラグイン化すれば、次回からは楽をできるし、他の人も助かると思うのですが誰も面倒でやってないという状況ですね。 自分もやる気がないです。実際に手を動かして設定することで Nx の仕組みや設定方法がよく分かる(次回も手作業すると思い出す) この記事のようにやり方がまとまっていれば、作業が酷く面倒というほどでもない どうせプロジェクトセットアップの最初にやるだけ プラグイン化するには、様々な状況下を想定して、プラグインが追加する設定が他に干渉をしないかを考える必要がある(手作業なら作業者が考えて回避したり調整できる) 機械学習のプロジェクトにアサインされてしまったので、暫く Web 開発することはなさそうあたりが理由です。 最初の理由が大きいですね。こういったツールは便利ですが、トラブった時に対処できるようになっておきたいので、中身の仕組みをある程度把握しておきたいと考えてしまいます。 最後のは完全に自分個人の事情ですが、専門が特にないエンジニアなので、必ずしも Web 開発してるってわけではないんです。 作るモチベーションがあっても、メンテするモチベーションはないなぁと。 この記事を読んでプラグイン化することに興味を持った方は、チャレンジしてみてはどうででしょうか?
Pugの文法まとめ
Pug の文法について網羅的に説明された日本語のサイトが見つからなかったので、自分でまとめてみました。 英語でよければ公式にしっかりしたドキュメントがあります。 この記事はその内容の中でも特に文法に絞って、重要な順番で解説しています。 できるだけリファレンスを網羅している長い記事ですが、大事な順に書いているので、単に HTML を簡潔に書くためだけなら、最初の方の「HTML を簡潔に書くための基本文法」だけ読んでおけば大丈夫です。 目次 Pug って何? HTML を簡潔に書くことができるものです。HTML に変換されます。昔は Jade と呼ばれていましたが名前が変更されました。 単に HTML を簡潔に書けるだけでなく、テンプレートエンジン的な機能も持っています。他のファイルを include する レイアウトを他のファイルに書いて extends して内容を埋め込む 変数を定義する ループしたり条件分岐したり Markdown ファイルを HTML に変換して埋め込んだり、Sass を CSS に変換して埋め込んだりといったことができます。 HTML を簡潔に書くための基本文法 まずはテンプレートエンジン的な機能は置いておいて、HTML を簡潔に書く記法について解説します。 タグ 以下の HTML が Pug でどうなるか見てみましょう。 <main> <article> <header> <h1>記事タイトル</h1> </header> <section> <p>記事サンプルです。</p> </section> </article> </main>Pug は以下のようになります。 main article header h1 記事タイトル section p 記事サンプルです。タグを閉じるのではなく、インデント(行の先頭から続く空白の数)でタグの入れ子構造を表します。 doctype は以下のように記述します。 doctype html html head body↓ HTML に変換すると... <!DOCTYPE html> <html> <head></head> <body></body> </html>属性 以下のように属性があるタグをどう書くかの基本を解説します。 基本 <a id="google-link" class="button small" href="//google.com">Google</a>タグの属性は括弧の中に入れて表します。(文字列を囲うのはシングルクォーテーション、ダブルクォーテーションのどちらも使えます) a(id='google-link' class='button small' href='//google.com') Google属性と属性の間にコンマを入れても構いません。 a(id='google-link', class='button small', href='//google.com') Google属性を複数行に分けて書くこともできます。 a( id='google-link' class='button small' href='//google.com' ) Googleid の # 記法 id は特別に # を使った記法が使えます。 a#google-link(class='button small' href='//google.com') Googleなお、div タグの場合はタグ名を省略できます。 #link a#google-link(class='button small' href='//google.com') Google↓ <div id="link"> <a id="google-link" class="button small" href="//google.com">Google</a> </div>class の . 記法 クラスにも特別に . を使った記法が用意されています。複数つなげて指定できます。 a#google-link.button.small(href='//google.com') Googleこちらも id と同様に div を省略できます。 .link a#google-link.button.small(href='//google.com') Google↓ <div class="link"> <a id="google-link" class="button small" href="//google.com">Google</a> </div>style 属性 タグに直接スタイルを指定するときは以下のように書けます。 a(style={color: 'red', background: 'green'})Boolean 属性 真偽値を表す属性は true か false を受け付けます。値を指定しなければ true だとみなされます。 input(type='checkbox' checked)input(type='checkbox' checked=true)input(type='checkbox' checked=false)false を指定した場合、Pug はその属性を出力しません。 true の場合、Pug はその文書の doctype が HTML かどうかで適切に出力します。 出力の詳しい動作については後述する Boolean 属性の出力 を参照。 文字列(Plain Text) タグの後に続けて中身の文字列を記述することができます。 p 記事サンプルです。複数行に分けて書きたい場合は、パイプ( | ) を利用した記法があります。 p | これはサンプルです。 | Pugのパイプを使って複数行に分けて記述しています。これは以下の HTML になります。パイプを使って複数行に分けて書いたものは、まとめてタグの中身になります。 <p>これはサンプルです。Pugのパイプを使って複数行に分けて記述しています。</p>パイプを使うと、文章中にインラインタグを埋め込むのは以下のように書けます(後述の もうちょっと高度な話 で説明する #[] を使って埋め込むこともできます。)。 p | 文章の中に span.keyword インラインタグ | を埋め込むことができます。 br | 改行タグも埋め込めます。↓ <p> 文章の中に<span class="keyword">インラインタグ</span>を埋め込むことができます。<br /> 改行タグも埋め込めます。 </p>コメント HTML として出力されるコメントは以下のように書きます。 // これはHTMLに出力されるコメントです。 p foo以下のように HTML のコメントとして出力されます。 <!-- これはHTMLに出力されるコメントです。 --> <p>foo</p>HTML に出力して欲しくないコメントは以下のようにハイフンを追加して書きます。 //- これはHTMLに出力されないコメントです。 p foo複数行にわたるブロックコメントも記載できます。 body //- これはHTMLには出力されないコメントです。 複数行に渡るブロックコメントです。 // これはHTMLに出力されるコメントです。 複数行に渡るブロックコメントです。テンプレート構文 単に HTML を Pug でシンプルに記述したいだけであれば、覚えることは今まで説明しただけで事足ります。 他のテンプレート構文と組み合わせて使うなら、これから説明する内容は覚えなくてもいい・・・というより、混ぜると訳がわからなくなるので Pug のテンプレート構文は使うべきではないでしょう。 他のテンプレート構文と組み合わせて使うというのは、例えば Laravel のテンプレートエンジン Blade と組み合わせる とか、React で利用する とか Vue.js で利用する とか、Angular で利用する とかを指しています [^2]。この場合は、後述の もうちょっと高度な話 のうち、HTML 記述に関係するところには目を通しておくと良いかと思います。 [^2]: React をテンプレート構文と呼ぶのは違和感ありますが Pug の公式では変数の定義に var キーワードを使って説明されています。古いブラウザ(IE10 以下とか)でも動くようにという配慮からだと思いますが、もう var は使わず let や const を使うべきかと。少なくとも Node.js 上で Pug を HTML に変換するのであればブラウザの対応は関係ありません。ここでは let や const で説明します。 JavaScript コード ここで説明するのは HTML に含めるブラウザで動作させる JavaScript のことではなく、Pug が変換時に実行する JavaScript のことです。 Pug では HTML への変換時に JavaScript を実行する機能が用意されています。とはいえ、Node.js パッケージを読み込んで利用するような高度なことはできません。 Unbuffered Code 頭に -(ハイフン)を付ける記述方法で、HTML タグとは無関係に JavaScript コードを実行します。 Pug では HTML として出力されるものを buffered, 出力されないものを unbuffered と呼んでいます。 最もよく利用するのは変数の定義でしょう。 - const list = ["Uno", "Dos", "Tres", "Cuatro", "Cinco", "Seis"]このように - (ハイフン)を頭に付けると JavaScript コードと認識されます。 以下のように複数行に分けて記述することもできます。 - const list = ["Uno", "Dos", "Tres", "Cuatro", "Cinco", "Seis"]Buffered Code タグの中身に JavaScript コードの実行結果を埋め込みます。 //- イコール(=)を使ってタグの中身に JavaScript の結果を埋め込める p = 'This code is' + ' <escaped>' //- 1行で書くこともできる p= 'This code is' + ' <escaped>'タグの内容は HTML エスケープされます。 <p>This code is <escaped>!</p> <p>This code is <escaped>!</p>エスケープして欲しくない場合は = ではなく != を使います。次の「変数の埋め込み」で説明します。 変数の埋め込み エスケープする場合 #{変数名} で変数の内容を文字列に埋め込むことができます。また先ほど説明した Buffered Code を使って h1= 変数名 とか p= 変数名 の形でタグの中に変数の内容を埋め込めます。内容が埋め込まれる前に HTML はエスケープされます。 - const title = "On Dogs: Man's Best Friend"; - const author = "enlore"; - const theGreat = "<span>escape!</span>";h1= title p Written with love by #{author} p This will be safe: #{theGreat}出力される HTML は以下になります。 <h1>On Dogs: Man's Best Friend</h1> <p>Written with love by enlore</p> <p>This will be safe: <span>escape!</span></p>変数の埋め込みと紹介していますが、もちろん JavaScript のコードを実行して結果を埋め込むこともできます。 h1= title.toUpperCase() p Written with love by #{author.toUpperCase()}#{ 自体を文字列に含めたい場合は、\#{ のようにスラッシュでエスケープするか、#{} の中に入れて JavaScript の文字列として評価させる方法が取れます。 p Escaping works with \#{interpolation} p Interpolation works with #{'#{interpolation}'} too!以下の HTML に変換されます。 <p>Escaping works with #{interpolation}</p> <p>Interpolation works with #{interpolation} too!</p>エスケープしない場合 埋め込む文字列をエスケープしたくない場合は != や !{} を利用します。 当然ですがユーザーの入力をそのまま埋め込むのは危険です。 - const title = "On Dogs: <span class=\"subtitle\">Man's Best Friend</span>" - const theGreat = "<span>escape!</span>"; title!= title p This will be unsafe: !{theGreat}以下のように変数内の HTML タグがそのまま出力されます。 <h1>On Dogs: <span class="subtitle">Man's Best Friend</span></h1> <p>This will be safe: <span>escape!</span></p>条件分岐 if まずは if 文から。 - const user = {description: 'foo bar baz'} - const authorised = false #user if user.description h2.green Description p.description= user.description else if authorised h2.blue Description p.description. User has no description, why not add one... else h2.red Description p.description User has no descriptionunless if の逆の「もし〜でなければ」を表す unless が用意されています。 unless user.isAnonymous p You're logged in as #{user.name}これは if を使って以下を書いた場合と同じになります。 if !user.isAnonymous p You're logged in as #{user.name}case JavaScript では switch 文に相当するものですね。 - const friends = 10 case friends when 0 p you have no friends when 1 p you have a friend default p you have #{friends} friendsbreak は書かなくても勝手に下まで実行していったりしません。もし複数の条件で同じ内容を実行したい場合は以下のように記述できます。 - const friends = 0 case friends when 0 when 1 p you have very few friends default p you have #{friends} friendsある条件のときには何もしないという場合は、明示的に break を記述する必要があります。 - const friends = 0 case friends when 0 - break when 1 p you have very few friends default p you have #{friends} friends繰り返し処理 each 集合の中を順に処理する each が用意されています。ほとんどの場合、繰り返し処理で使うのはこれです。 - const values = [1, 2, 3, 4, 5] ul each val in values li= val↓ <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul>values の部分に JavaScript のコードを直接書いても構いません。 ul each val in [1, 2, 3, 4, 5] li= valul each val in values.length ? values : ['There are no values'] li= val値だけでなくインデックスも受け取れます。 ul each val, index in ['zero', 'one', 'two'] li= index + ': ' + valオブジェクトをループさせることもできます。(注: each の値に指定する変数は 値, キー の順番) - const dict = {1: 'one', 2: 'two', 3: 'three'} ul each val, key in dict li= key + ': ' + valwhile 条件に一致する間ループを回す while 文も利用できます。 - let n = 0; ul while n < 4 li= n++↓ <ul> <li>0</li> <li>1</li> <li>2</li> <li>3</li> </ul>他のファイルの埋め込み (inlucde) 他のファイルの内容を取り込みます。 例えば index.pug が以下の内容とします。 //- index.pug html //- ここで includes/_head.pug を読み込み include includes/_head.pug body h1 My Site p Welcome to my super lame site. //- ここで includes/_foot.pug を読み込み include includes/_foot.pugincludes/_head.pug は以下の内容とします。 //- includes/_head.pug head title My Site script(src='/javascripts/jquery.js') script(src='/javascripts/app.js')includes/_foot.pug は以下の内容とします。 //- includes/_foot.pug footer#footer p Copyright (c) foobarindex.pug を HTML に変換すると、inculdes/_head.pug と includes/_foot.pug の内容が埋め込まれて以下のようになります。 <!DOCTYPE html> <html> <head> <title>My Site</title> <script src="/javascripts/jquery.js"></script> <script src="/javascripts/app.js"></script> </head> <body> <h1>My Site</h1> <p>Welcome to my super lame site.</p> <footer id="footer"> <p>Copyright (c) foobar</p> </footer> </body> </html>平文テキストの include Pug ではないファイルを取り込むこともできます。例えば CSS ファイルや JavaScript ファイルですね。 //- index.pug doctype html html head style include style.css body h1 My Site p Welcome to my super lame site. script include script.js/* style.css */ h1 { color: red; }// script.js console.log('You are awesome');index.pug は以下の HTML に変換されます。 <!DOCTYPE html> <html> <head> <style> /* style.css */ h1 { color: red; } </style> </head> <body> <h1>My Site</h1> <p>Welcome to my super lame site.</p> <script> // script.js console.log('You are awesome'); </script> </body> </html>Markdown ファイルの埋め込み (Filter) Pug には 後述する Filter という機能 があります。これを使うと Markdown や SASS などの変換処理を行った上で埋め込むことができます。 //- index.pug doctype html html head title An Article body include:markdown-it article.md# article.mdThis is an article written in markdown.index.pug を変換すると以下の HTML になります。 <!DOCTYPE html> <html> <head> <title>An Article</title> </head> <body> <h1>article.md</h1> <p>This is an article written in markdown.</p> </body> </html>共通レイアウトの定義 (extends) include では例えばヘッダやフッタなどの複数ページで利用する共通部品を分離することができました。 そうではなく、全体で共通に利用するレイアウトを用意して、その中にページごとに異なる部分を埋め込みたい時に extends が利用できます。 基本 レイアウトファイルを以下のように作成します。 //- _layout.pug//- 外から block で変数(title)を渡せるようにする block variableshtml head //- title はページごとに異なるので外から与えられたものを埋め込む title My Site - #{title} //- ページによって利用する js ファイルが異なるので block で渡せるようにする block scripts //- もし block scripts が渡されなければ、デフォルト値として以下を利用 script(src='/jquery.js') body //- ページのコンテンツは block で渡せるようにする block content //- ページによってはフッタが異なるので block で渡してカスタマイズできるようにする block foot //- もし block foot が渡されなければ、デフォルト値として以下を利用 #footer p some footer contentこれを利用するページは以下のように extends を使います。 //- page-a.pug extends _layout.pug//- タイトルを変数で渡す block variables - const title = 'page A'//- jquery 以外に pets.js というページ独自の js ファイルも利用するのでデフォルト値を上書き block scripts script(src='/jquery.js') script(src='/pets.js')//- ページのコンテンツを渡す block content h1= title - var pets = ['cat', 'dog'] each petName in pets p= petName//- フッタはデフォルトのものを利用(block foot は渡さない)↓ page-a.pug を HTML に変換すると <html> <head> <title>My Site - page A</title> <script src="/jquery.js"></script> <script src="/pets.js"></script> </head> <body> <h1>page A</h1> <p>cat</p> <p>dog</p> <div id="footer"> <p>some footer content</p> </div> </body> </html>append / prepend (デフォルト値の前後に追加) 今までの説明では block に指定するとデフォルト値を上書き(置き換え)していました。 そうではなく、デフォルト値にさらに追加するための方法が用意されています。 例えば先の例で page-a.pug は jquery.js の他に pet.js も利用するために //- jquery 以外に pets.js というページ独自の js ファイルも利用するのでデフォルト値を上書き block scripts script(src='/jquery.js') script(src='/pets.js')と記述していました。 これは append を利用して以下のように記述できます。 //- デフォルト値である jquery.js の後に pets.js というページ独自の js ファイルの利用を追加 block append scripts script(src='/pets.js')append でなく prepend を指定するとデフォルト値の前に追加されます。 レイアウトを継承してサブレイアウトを定義 サイト全体で利用する _layout.pug を作ったわけですが、幾つかのページではさらにコンテンツ部分の構造が同じなので共通化したいとします。 以下のように _layout.pug を継承した _sub-layout.pug を作成します。 //- _sub-layout.pug extends _layout.pug//- コンテンツ部分の構造をさらに共通化 block content .sidebar block sidebar p nothing .primary block primary p nothingこれで sidebar と primary というブロックが content の内部に作られました。 これを以下のように利用できます。 //- page-b.pug extends _sub-layout.pug block variables - const title = 'page B' block sidebar p Sidebar block primary p Primary↓ <html> <head> <title>My Site - page B</title> <script src="/jquery.js"></script> </head> <body> <div class="sidebar"> <p>Sidebar</p> </div> <div class="primary"> <p>Primary</p> </div> <div id="footer"> <p>some footer content</p> </div> </body> </html>extends の継承については公式が注意書きをしているので、多用したり複雑な使い方をするのであれば extends 利用のよくある間違い には目を通しておいた方がいいでしょう。 Mixin 同じ部品を色々な箇所で利用したい場合、別ファイルにして include を利用する方法を既に説明しました。 でも1つのファイルでしか使わないので別ファイルにするほどでも・・・という場合もあります。 また小さい部品を幾つか集めて1つのファイルで管理したいということもあるでしょう。 Mixin はプログラム言語で言うところの関数みたいなものです。 あるいは独自タグを定義するような、SPA フレームワークでのコンポーネント的なものとも言えるかもしれません。 //- Mixinの宣言 mixin list ul li foo li bar li baz//- Mixinを利用するときは頭に + を付ける +list +list↓ <ul> <li>foo</li> <li>bar</li> <li>baz</li> </ul> <ul> <li>foo</li> <li>bar</li> <li>baz</li> </ul>Mixin に引数を渡す 引数を渡すこともできます。 mixin pet(name) li.pet= nameul +pet('cat') +pet('dog') +pet('pig')引数のデフォルト値もサポートしています。 mixin article(title='Default Title') .article .article-wrapper h1= title//- 引数省略してデフォルト値を利用 +article()+article('Hello world')可変長引数もサポートしています。 //- 最初の引数は id で、その後は items に配列として格納 mixin list(id, ...items) ul(id=id) //- items を順にループ each item in items li= item//- 最初の 'my-list' は id、残りは items +list('my-list', 1, 2, 3, 4)↓ <ul id="my-list"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul>Mxin にブロックを渡す block を渡すこともできます。 mixin article(title) .article .article-wrapper h1= title if block //- ブロックが渡されてたらそれを埋め込む。 block else //- ブロックが渡されてなかったら以下 p No content provided//- ブロックを渡さないで呼び出し +article('Hello world')//- ブロックを渡して呼び出し +article('Hello world') p This is my p Amazing article↓ <div class="article"> <div class="article-wrapper"> <h1>Hello world</h1> <p>No content provided</p> </div> </div> <div class="article"> <div class="article-wrapper"> <h1>Hello world</h1> <p>This is my</p> <p>Amazing article</p> </div> </div>1つ目は "No content provided" が、2つ目は渡したブロックが埋め込まれていますね。 Mixin に属性を渡す Mixin では渡された属性を attributes で参照できます。 mixin link(href, name) //- 属性として渡される class を、a タグの class に設定(!= を使ってるのでエスケープはしない) a(class!=attributes.class href=href)= name//- 属性として class="btn" を渡している +link('/foo', 'foo')(class="btn")class!=attributes.class としてエスケープしないようにしているのは、Mixin を呼び出す際の class="btn" で既にエスケープされてるので、もう一度エスケープするのが無駄だからです。 渡された属性をそのまま全部展開したい場合は &attributes が利用できます。 mixin link(href, name) a(href=href)&attributes(attributes)= name+link('/foo', 'foo')(class="btn")渡された属性 class="btn" がそのまま link Mxin 内の属性として展開されます。 <a class="btn" href="/foo">foo</a>引数なしの Mixin に属性を渡す場合、+link(class="btn") と書いてもそれが引数でなく属性だと認識されます。 ただ Pug 公式では +link()(class="btn") のように引数がないことを明示する記述をお勧めしています。 Filter Pug に渡す前に変換処理を行う仕組みです。例えばマークダウンを HTML に変換するとか、SCSS を CSS に変換して埋め込むとかができます。 Filter は頭に : を付けて呼び出します。括弧の中にはフィルタに渡すオプションを指定できます。 :markdown-it(linkify langPrefix='highlight-') # Markdown Markdown document with http://links.com and ```js var codeBlocks; ```↓ <h1>Markdown</h1> <p>Markdown document with <a href="http://links.com">http://links.com</a> and</p> <pre><code class="highlight-js">var codeBlocks; </code></pre>この解説は文法の説明に絞っており、Pug の実行方法には触れません^3が、markdown-it フィルタを利用するにはそのためのパッケージのインストールが必要です。 全ての JSTransformer モジュールがフィルターとして利用可能です。 注意点として、フィルタに Pug の処理中に JavaScript で生成した動的なものを渡すことはできません。 ちょっとわかりにくいので以下の例で説明します。 - const mds = [ '# タイトル1', '# タイトル2', ]each md in mds :markdown-it(linkify langPrefix='highlight-') md上記で md の部分は変数の内容を渡したいのですが、このままでは単に "md" という文字列としてしか認識してくれません。 :markdown-it(linkify langPrefix='highlight-') #{md}のように #{md} で渡しても、フィルタの中身は単なる文字列としてしか認識されず、"#{md}" という文字列が渡されるだけです。 なおフィルタのオプションにも変数を渡したり JavaScript で動的に生成したものを渡したりできません。 JavaScirpt で生成したものをフィルタに渡す機能が一切提供されていないんですね。 高速に動作させるためとのことですが、Pug を静的サイトジェネレータとして利用しようとすると、これが大きな制限になります。 もうちょっと高度な話 タグ 文字列中へのタグの埋め込み 長い文字列の中にインラインタグを埋め込むときに #[] でタグを埋め込む構文を覚えておくと楽に書けます。 これを知らなくても |(パイプ)を使って書けるので基本としては紹介しませんでした。 p. This is a very long and boring paragraph that spans multiple lines. Suddenly there is a #[strong strongly worded phrase] that cannot be #[em ignored]. p. And here's an example of an interpolated tag with an attribute: #[q(lang="es") ¡Hola Mundo!]以下の HTML に変換されます。 <p> This is a very long and boring paragraph that spans multiple lines. Suddenly there is a <strong>strongly worded phrase</strong> that cannot be <em>ignored</em>. </p> <p> And here's an example of an interpolated tag with an attribute: <q lang="es">¡Hola Mundo!</q> </p>入れ子を1行で表現 a imgを以下のように1行で記述する方法が用意されています。 a: imgcase を短く書きたい場合にも使えます。 - const friends = 1 case friends when 0: p you have no friends when 1: p you have a friend default: p you have #{friends} friends閉じタグのいらないタグ img のような閉じタグのいらないタグを Pug は自動的に判別してそのように変換してくれます。 a imgこれは以下の HTML になります。 <a><img /></a>HTML 5 に規定されているタグなら勝手に判別してくれますが、コンポーネントなどの独自タグの場合はそうはいきません。以下のように閉じタグがないことを後ろにスラッシュを付けて伝える必要があります。 foo(bar='baz')/以下の HTML に変換されます。 <foo bar="baz" />属性 エスケープの抑止 Pug はデフォルトで全ての属性の値をエスケープします。エスケープして欲しくない場合は = ではなく != を使います。 div(escaped="<code>") div(unescaped!="<code>")上記が下記の HTML に変換されます。 "<" が < に、 ">" が > にエスケープされるかどうかが異なっています。 <div escaped="<code>"></div> <div unescaped="<code>"></div>特殊文字が含まれる場合 SPA フレームワークと組み合わせて利用すると、属性に [] とか () などの特殊文字が使われることがあります。そう Angular のことですね。そういう特殊文字が含まれる場合は '' や "" で囲ってやってください。 button(class='button' '(click)'='play()')Boolean 属性の出力 doctype html を指定していない場合、Pug は文書が HTML かどうか分からない(例えば XML かもしれない)ので、値として属性名を出力します。 input(type='checkbox' checked)input(type='checkbox' checked=true)↓ どちらも以下になります。値には属性名である "checked" が出力されます。 <input type="checkbox" checked="checked" />false の場合、属性自体を出力しません。 input(type='checkbox' checked=false)↓ <input type="checkbox" />文書に doctype html を指定している場合、Pug は HTML の真偽値の表し方で出力します。 つまり true だった場合に属性名を値として出力しません。 input(type='checkbox' checked=true)↓ checked という属性に値は設定されません。 <input type="checkbox" checked />なおこのルールはあくまで指定したのが真偽値だった場合のみ適用されます。 例えば以下の場合、値が true または false の真偽値ではなく、"true" という文字列なので、上記のルールは適用されずにそのまま文字列として出力されます。 input(type='checkbox' checked=true.toString())input(type='checkbox' checked='true')input(type='checkbox' checked=true && 'true')↓ 上記3つはどれも以下のように値に文字列がそのまま出力されます。 <input type="checkbox" checked="true" />&attributes 実はこれは既に「Mixin に属性を渡す」で紹介しています。 JavaScript のオブジェクトをそのまま属性として展開します。 div#foo(data-bar="foo")&attributes({'data-foo': 'bar'})↓ <div id="foo" data-bar="foo" data-foo="bar"></div>もちろん変数に入れたオブジェクトも展開されます。 - const attributes = { class: 'baz' } div#foo(data-bar="foo")&attributes(attributes)↓ <div class="baz" id="foo" data-bar="foo"></div>extends 利用のよくある間違い 公式がわざわざ注意書きを書いているので、説明しておきます。 Pug のテンプレート継承機能は複雑なページ構造を小さくシンプルなファイルに分解する強力な機能だけれども、あまり多くのテンプレートを繋げてしまうと余計複雑になってしまいます^4。 extends を使う子供テンプレートのトップレベル(つまりインデントされていない部分)には block と Mixin 定義だけが現れるようにすべきです。 これは重要です!親のテンプレートはページ全体の構造を定義し、子供のテンプレートはただ特定のブロックのロジックやマークアップを append / pretend したり置き換えるだけにすべきです。 このことはマークアップを含むことのできる unbuffered code(頭が -(ハイフン)で始まる JavaScript コード)にも当てはまります。 同じ理由で // 始まりの HTML に出力されるコメントも子供テンプレートのトップレベルに現れてはいけません。 (//- 始まりの HTML に出力しない Pug コメントはどこに書いても構いません。)