メタ言語の続きです。前回はTypeScriptをモジュール分割しました。EJSやSass(SCSS)と違ってモジュール部分のソースは含まれず、またエラーも起きてうまく動きません。今回はエラーについて掘り下げてみました。*1。それではいきましょう!
【目次】
- 前回のおさらい(モジュール分割→エラー)
- exports is not definedエラーはCommonJSが原因
- tsconfigのmoduleをes2015に変更してみる(結果変わらず…)
- gulpfile.jsのTypeScript設定を変更
- モジュールファイルもコンパイル!
- head部分にグローバル変数を書き足す
- 次のエラー:require is not defined
- headでモジュールファイルもリンクしてみる(結果変わらず…)
- 最後に:まとめ(次回はWebpack!)
※参考:前回記事
【TypeScript】おみくじJSをTypeScriptに移植:型付け→モジュール分割(エラーあり) - クモのようにコツコツと
※参考:【メタ言語】HTMLテンプレートエンジン、AltCSS、AltJSのまとめ
qiita.com
前回のおさらい(モジュール分割→エラー)
「omikuji.ts」のコード
// 運勢 const unsei: string[] = [ "大吉", "吉", "中吉", "小吉", "末吉", "凶", "大凶" ]; // 結果 let kekka = (): string => { let nmb: number = Math.floor(Math.random() * unsei.length ); return unsei[nmb]; } // おみくじ export let omikuji = (): void => { let massage: string = 'あなたの運勢は「' + kekka() + '」です。'; alert(massage); }
おみくじ部分をモジュールとして分割したファイル。
「script.ts」のコード
import { omikuji } from "./omikuji/omikuji"; // イベント実行 document.addEventListener("DOMContentLoaded", () => { let btn: HTMLButtonElement = document.querySelector('.inner__text-btn') as HTMLButtonElement; btn!.addEventListener('click', omikuji, false); }, false);
omikujiモジュールをインポートしている。
コンパイル後の「script.js」
"use strict"; exports.__esModule = true; var omikuji_1 = require("./omikuji/omikuji"); // イベント実行 document.addEventListener("DOMContentLoaded", function () { var btn = document.querySelector('.inner__text-btn'); btn.addEventListener('click', omikuji_1.omikuji, false); }, false);
omikujiモジュール部分のコードは含まれない。
冒頭のexports.__esModule = true
部分に下記のエラーが…
Uncaught ReferenceError: exports is not defined
詳細は前回の記事を参照。
※参考:【TypeScript】おみくじJSをTypeScriptに移植:型付け→モジュール分割(エラーあり) - クモのようにコツコツと
exports is not definedエラーはCommonJSが原因
エラーコードUncaught ReferenceError: exports is not defined
について調べる。
まずこのX is not defined
エラーは下記のような意味合い。
- 変数が定義されてない
- または関数のスコープから外れている
※参考:ReferenceError: "x" is not defined - JavaScript | MDN
今回は冒頭にあるexports
の定義が見つからないよ、というエラー。
こちらの記事によるとCommonJSの設定が原因らしい。
tsconfigのmoduleが commonjs になってたのが原因でした。 es2015 に変えたらこのエラーは出なくなりました。
※参考:jsで書いたフロントを徐々にtsに移行する - Qiita
TypeScriptの設定ファイル「tsconfig」を開いてみる。
{ "compilerOptions": { // 中略 "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ // 中略 }
お、確かに。module
がcommonjs
になっとる。
コメントの和訳
モジュールコードの生成を指定: 'none'、 'commonjs'、 'amd'、 'system'、 'umd'、 'es2015'、 'es2020'、または 'ESNext'。
exports
について調べると確かにこの書き方はCommonJS
の書き方だわ。
CommonJS Export/Import exports.method = method export { method }
※参考:# CommonJS と ES6の import/export で迷うなら - Qiita
tsconfigのmoduleをes2015に変更してみる(結果変わらず…)
「tsconfig」のmodule
をes2015
に変えてみる。
{ "compilerOptions": { // 中略 module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ // 中略 } "
コンパイル結果
exports.__esModule = true;
うーむ、変わらん。。
こちらの記事のようにのimport
、export
のESモジュールになることを期待していた。
※参考:tsconfigのmoduleの設定値の違い: commonjs, amd, system, umd, es6, es2015, esnext, none - Qiita
ESモジュールの件はひとまず置いておく…。
gulpfile.jsのTypeScript設定を変更
もう一つ、「index.js」がEJSやSass(SCSS)のようにモジュールのコードを含んでいなかったため、モジュールファイルもコンパイルしたい。
前回はindex.jsしかコンパイルされなかった。
「gulpfile.js」のTypeScript設定部分を変更する。
まずはコンパイル部分。
// TypeScriptコンパイル gulp.task('ts', (done) => { gulp.src('./src/ts/**/*.ts') .pipe(plumber()) .pipe(typescript()) .js .pipe(gulp.dest('./dest/js')); done(); });
EJSやSass(SCSS)と同様にgulp.src()
に/**
を追記。下層フォルダのファイルも適用範囲にする。
watch(監視)タスクも変更
// 監視ファイル gulp.task('watch-files', (done) => { gulp.watch(["./src/ejs/**/*.ejs", "./src/json/**/*.json"], gulp.task('ejs')); gulp.watch("./dest/*.html", gulp.task('browser-reload')); gulp.watch("./src/scss/**/*.scss", gulp.task('sass')); gulp.watch("./dest/css/*.css", gulp.task('browser-reload')); gulp.watch("./src/ts/**/*.ts", gulp.task('ts')); gulp.watch("./dest/js/*.js", gulp.task('browser-reload')); done(); });
こちらもts
部分に他と同様に/**
を追記する。
モジュールファイルもコンパイル!
変更を適用するためにControl + cでgulpを一旦閉じる。
gulp再起動!
npx gulp
お、下層フォルダのファイルもコンパイルされるようになった!
「omikuji.js」のコード
"use strict"; exports.__esModule = true; exports.omikuji = void 0; // 運勢 var unsei = [ "大吉", "吉", "中吉", "小吉", "末吉", "凶", "大凶" ]; // 結果 var kekka = function () { var nmb = Math.floor(Math.random() * unsei.length); return unsei[nmb]; }; // おみくじ exports.omikuji = function () { var massage = 'あなたの運勢は「' + kekka() + '」です。'; alert(massage); };
こちらもESモジュールではなくCommonJSのexports
にコンパイルされている…。
デベロッパーツールを見ると「script.js」しか認識されてないようだ。
「omikuji.js」はファイルとしては存在しているが、リンク状態にはなっていないということだ。
head部分にグローバル変数を書き足す
やはり先ほどのエラーを解決しないとモジュールファイルのコードが認識されない。
上記のQAを参考にhead部分にグローバル変数を追記してみる。
<script> var exports = {}; </script>
「ejs/base/_head.ejs」
<meta charset="UTF-8"> <title><%= main.title %></title> <meta name="description" content="<%= main.lede %>"> <link rel="stylesheet" href="css/style.css"> <script> var exports = {}; </script> <script src="js/script.js"></script>
script.jsのリンクの前の行に追記。
そうすると最初のエラーは無くなった!ハックっぽい手段だがこれでええのんか?
他には下記のような回答もあった
- typescriptのバージョンを下げる
- コアな設定ファイルを書き換える
今後のメンテナンスを考えるとどちらも好ましくない。
次のエラー:require is not defined
exports
は認識されるようになったが、次に別のエラーが…
omikuji.jsをインポートしている行。
エラーメッセージ
Uncaught ReferenceError: require is not defined
今度はrequire
が見つからないよと。
そう、そもそもrequire
はサーバサイドであるNode.jsのメソッドのため、クライアントサイド(ブラウザ)では動かない。だからESモジュールにしたかったんだよなー…。
headでモジュールファイルもリンクしてみる(結果変わらず…)
こちらの記事にいくつか対処法があった。
※参考:Uncaught ReferenceError: require is not defined 対処法まとめ - 文系プログラマー
まずインポートファイルである「omikuji.js」もheadからリンクしてみる。
「ejs/base/_head.ejs」
<meta charset="UTF-8"> <title><%= main.title %></title> <meta name="description" content="<%= main.lede %>"> <link rel="stylesheet" href="css/style.css"> <script> var exports = {}; </script> <script src="js/script.js"></script> <script src="js/omikuji/omikuji.js"></script>
デベロッパーツールで見ると「omikuji.js」は認識されるようになった!
しかし、結局のところまだrequire
は認識されない…。
Uncaught ReferenceError: require is not defined
先ほどの参考記事では他には、他に下記のような方法が書いてある。
- Browserifyでモジュールをバンドルする
- Webpackでモジュールをバンドルする
EJSやSass(SCSS)もモジュールを統合させた1ファイルのリンクで済んでいた。
JSファイルもやはりバンドルする方向がいいなーと思った。headファイルにリンクをたくさん書く方法ではモジュールの把握が難しくなりそう。
Browserify とWebpackでは今はWebpackの方が主流なのでこちらの方法を試すか。
※参考:JavaScriptのモジュール管理(CommonJSとかAMDとかBrowserifyとかwebpack) | tsuchikazu blog
Webpackは以前も何度か触っている。
※参考:webpackでJSのモジュールファイルをバンドルする - クモのようにコツコツと
最後に:まとめ(次回はWebpack!)
今回やってみたこと、わかったことをまとめます。
- exports is not definedエラー
→CommonJSが原因だった - tsconfigのmoduleをes2015に変更
→結果変わらず… - gulpfile.jsのTypeScript設定を変更
→/**
→モジュールファイルもコンパイルされた! - head部分にグローバル変数を書き足す
→exports
が認識されるようになった! - require is not definedエラー
→require
はCommonJSなのでブラウザでは動かない - headでモジュールファイルもリンク
→結果変わらず…
結局、CommonJSのをESモジュールに変更することはできなかったため、ブラウザでは認識されない。
また、認識されたとしてもモジュールファイルが分割されたままただとページごとのリンク設定の把握が難しそう。
ということで、次回はWebpackによるモジュールのバンドル(統合)をやってみたく思います。
それではまた!
※参考:【メタ言語】HTMLテンプレートエンジン、AltCSS、AltJSのまとめ
qiita.com
*1:結論から言うと、エラーは解消されず別アプローチとしてWebpackによるバンドルが必要なこと見えてきました、と言う内容です