• 最終更新日:

webpack5でPugとTypeScript、ESLint、Prettier、Airbnbの設定方法 - Babelによる古いブラウザ対策も紹介

webpack 5でTypeScriptとPugの設定方法 - BabelによるIE11対策も紹介

今回はwebpack5でPugとTypeScript、ESLint、Prettier、Airbnbの設定方法について説明します。また Babelを使った古いブラウザ対策も紹介します。

設定する内容は以下です。

  • Pugの設定
  • TypeScriptの設定
  • Dart Sass
  • ESLintとPrettierの設定
  • AirbnbによるESLintのルール強化
  • Babelによる古いブラウザ対応

説明する環境は以下です。

  • macOS Catalina v10.15.5
  • Visual Studio Code v1.57.0
  • webpack v 5.51.1
  • node.js v16.13.1
この記事の目次

webpack 5でTypeScriptとPugの設定方法

今回はPugを使っていますが、EJSを使う場合は以下の記事を参考にしてみてください。

フォルダ構成について

【src】フォルダで作業したものが、【dist】フォルダに出力されます。

【src】フォルダと【dist】フォルダの中身は以下です。

webpackをインストール

まずはpackage.jsonを作成します。VSCodeを使っているなら【control + shift + @】でターミナルが表示されるので、以下のコマンドを打ってpackage.jsonを作成します。

npm init -y

次に以下のコマンドを打ってwebpackをインストールします。

npm i -D webpack webpack-cli

TypeScriptとPugを含むパッケージをインストール

パッケージをインストールします。TypeScriptとPug以外は必要に応じてインストールしてください。

npm i -D pug #Pug
npm i -D pug-loader #Pug読み込み用
npm i -D html-webpack-plugin #HTML用
npm i -D ts-loader #TypeScript読み込み用
npm i -D typescript #TypeScript
npm i -D mini-css-extract-plugin #CSSの出力用
npm i -D css-loader #CSS読み込み用
npm i -D sass #Sass #Sass
npm i -D sass-loader #Sass読み込み用
npm i -D postcss-loader #Autoprefixerの設定用
npm i -D autoprefixer #Autoprefixer
npm i -D eslint  #ESLint用
npm i -D eslint-config-prettier #prettierと競合するルールをオフ
npm i -D eslint-config-airbnb-base #ESLintにAirbnbのルール拡張
npm i -D eslint-config-airbnb-typescript #ESLintにAirbnb TypeScriptのルール拡張
npm i -D eslint-plugin-import #importするファイルの拡張子問題解決
npm i -D eslint-plugin-unused-imports #使っていないimportは削除
npm i -D prettier #Prettier用
npm i -D copy-webpack-plugin #画像をコピー用
npm i -D webpack-dev-server #仮想サーバー立ち上げ用
npm i -D terser-webpack-plugin #パッケージのライセンス情報をjsファイルの中に含める
npm i -D webpack-watched-glob-entries-plugin #読み込むファイルを複数指定する
npm i -D @typescript-eslint/eslint-plugin #ESLintがTypeScriptを理解できるようにする
npm i -D @typescript-eslint/parser #ESLintがTypeScriptを理解できるようにする

webpackも含めて1行でインストールする場合は以下です。

npm i -D webpack webpack-cli pug pug-loader html-webpack-plugin ts-loader typescript mini-css-extract-plugin css-loader sass sass-loader postcss-loader autoprefixer eslint eslint-config-prettier eslint-config-airbnb-base eslint-config-airbnb-typescript eslint-plugin-import eslint-plugin-unused-imports prettier copy-webpack-plugin webpack-dev-server terser-webpack-plugin webpack-watched-glob-entries-plugin @typescript-eslint/eslint-plugin @typescript-eslint/parser

インストールができるとpackage.jsonにパッケージ名とバージョンが載っているので確認してみましょう。

一部のパッケージがインストールできない場合は?

他にもパッケージをインストールしていると、依存関係がwebpackでは解決できず、一部のパッケージがインストールできない場合があります。その場合は以下のコマンドで強制的にインストールすることもできます。ただ、互換性を無視するため動作の確認は必要になります。

npm i --legacy-peer-deps 【パッケージ名】

詳しくはこちらの記事を参考にしてみてください。

webpack.config.jsに設定を書く

webpackの設定を書くために、webpack.config.jsというファイルを作成して以下のように書いていきます。

//path モジュールの読み込み
const path = require('path');
//cssファイルの出力用
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
//パッケージのライセンス情報をjsファイルに含める
const TerserPlugin = require('terser-webpack-plugin');
//画像のコピー用
const CopyPlugin = require('copy-webpack-plugin');
// HTMLの読み込み用
const HtmlWebpackPlugin = require('html-webpack-plugin');
//読み込むファイルを複数指定する用
const WebpackWatchedGlobEntries = require('webpack-watched-glob-entries-plugin');

const enabledSourceMap = process.env.NODE_ENV !== 'production';

const filePath = {
  js: './src/js/',
  pug: './src/pug/',
  sass: './src/scss/',
};

/* Sassファイル読み込みの定義*/
const entriesScss = WebpackWatchedGlobEntries.getEntries([path.resolve(__dirname, `${filePath.sass}**/**.scss`)], {
  ignore: path.resolve(__dirname, `${filePath.sass}**/_*.scss`),
})();

const cssGlobPlugins = (entriesScss) => {
  return Object.keys(entriesScss).map(
    (key) =>
      new MiniCssExtractPlugin({
        //出力ファイル名
        filename: `./css/${key}.css`,
      })
  );
};

/* Pug読み込みの定義 */
const entries = WebpackWatchedGlobEntries.getEntries([path.resolve(__dirname, `${filePath.pug}**/*.pug`)], {
  ignore: path.resolve(__dirname, `${filePath.pug}**/_*.pug`),
})();
const htmlGlobPlugins = (entries) => {
  return Object.keys(entries).map(
    (key) =>
      new HtmlWebpackPlugin({
        //出力ファイル名
        filename: `${key}.html`,
        template: `${filePath.pug}${key}.pug`,
        //JS・CSS自動出力と圧縮を無効化
        inject: false,
        minify: false,
      })
  );
};
//TypeScript読み込みの定義
const entriesTS = WebpackWatchedGlobEntries.getEntries([path.resolve(__dirname, `${filePath.js}*.ts`)])();

const app = {
  entry: entriesTS,
  //出力先
  output: {
    filename: './js/[name].js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
  //仮想サーバーの設定 ※これはver4の書き方になるので注意。ver3と書き方が違う
  devServer: {
    //ルートディレクトリの指定
    static: path.resolve(__dirname, 'src'),
    open: true,
    //バンドルされたファイルを出力するかどうか。お好みで。
    //writeToDisk: true
  },
  //パッケージのライセンス情報をjsファイルの中に含める
  optimization: {
    minimizer: [
      new TerserPlugin({
        extractComments: false,
      }),
    ],
  },
  module: {
    rules: [
      {
        test: /\.pug$/,
        use: [
          {
            loader: 'pug-loader',
            options: {
              pretty: true,
            },
          },
        ],
      },
      {
        test: /\.ts$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },

      {
        // 対象となるファイルの拡張子(scss)
        test: /\.(sa|sc|c)ss$/,
        // Sassファイルの読み込みとコンパイル
        use: [
          // CSSファイルを抽出するように MiniCssExtractPlugin のローダーを指定
          {
            loader: MiniCssExtractPlugin.loader,
          },
          // CSSをバンドルするためのローダー
          {
            loader: 'css-loader',
            options: {
              //URL の解決を無効に
              url: false,
              // production モードでなければソースマップを有効に
              sourceMap: enabledSourceMap,
              // postcss-loader と sass-loader の場合は2を指定
              importLoaders: 2,
              // 0 => no loaders (default);
              // 1 => postcss-loader;
              // 2 => postcss-loader, sass-loader
            },
          },
          // PostCSS(autoprefixer)の設定
          {
            loader: 'postcss-loader',
            options: {
              // PostCSS でもソースマップを有効に
              sourceMap: enabledSourceMap,
              postcssOptions: {
                // ベンダープレフィックスを自動付与
                plugins: [require('autoprefixer')({ grid: true })],
              },
            },
          },
          // Sass を CSS へ変換するローダー
          {
            loader: 'sass-loader',
            options: {
              //  production モードでなければソースマップを有効に
              sourceMap: enabledSourceMap,
            },
          },
        ],
      },
    ],
  },
  // import 文で .ts ファイルを解決するため
  // これを定義しないと import 文で拡張子を書く必要が生まれる。
  resolve: {
    // 拡張子を配列で指定
    extensions: ['.ts', '.js'],
  },
  target: 'web',
  //プラグインの設定
  plugins: [
    ...cssGlobPlugins(entriesScss),
    ...htmlGlobPlugins(entries),
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, 'src/images/'),
          to: path.resolve(__dirname, 'dist/images'),
        },
      ],
    }),
  ],
  //source-map タイプのソースマップを出力
  devtool: 'source-map',
  // node_modules を監視(watch)対象から除外
  watchOptions: {
    ignored: /node_modules/, //正規表現で指定
  },
};

module.exports = app;

TypeScriptのトランスパイルの指定をする

TypeScriptをJavaScriptにトランスパイルする指定をtsconfig.jsonに書きます。ターミナルで以下のように打つとtsconfig.jsonが作成されます。

tsc --init

作成されたtsconfig.jsonを見てみると、いろいろ書かれていますが、重要な指定は以下です。

{
  "compilerOptions": {
    "target": "es5", //ES5に変換
    "module": "es2015", //モジュールをES Modulesとして出力
    "sourceMap": true, //soucemapを出力する
    "strict": true, //厳格な型チェック機能を有効にする
    "moduleResolution": "node", //node_modules からライブラリを読み込む
    "esModuleInterop": true, //CommonJS 形式のモジュールを、ES Modules でインポートできるようにする
    //以下はお好みで。
    "skipLibCheck": true, //型システムの精度を犠牲にすることで、コンパイル実行時間を削減
    "forceConsistentCasingInFileNames": true //import時にファイルパスの文字列で大文字小文字を区別するかどうか
  }
}

ESLintの設定

ESLintはコードにあるバグを見つけてくれたり、インデントやスペースなどコードスタイルに一貫性を持たせてくれるものです。

まずは.eslintrc.jsにという名前でファイルを作って設定を書いていきます。

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    //'plugin:react/recommended', airbnbにほど設定されているので削除可能
    'airbnb-base',
    'airbnb-typescript/base', //追加
    //'airbnb/hooks', //追加
    'plugin:@typescript-eslint/recommended', //型を必要としないプラグインの推奨ルールをすべて有効
    'plugin:@typescript-eslint/recommended-requiring-type-checking', //型を必要とするプラグインの推奨ルールをすべて有効
    'prettier', //追加 ESLintの情報に沿ってフォーマット
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 12, //latestから12に変更
    sourceType: 'module',
    tsconfigRootDir: __dirname, //追加 tsconfig.jsonがある相対パスを指定
    project: ['./tsconfig.json'], //追加  tsconfig.jsonを指定
  },
  plugins: ['@typescript-eslint', 'unused-imports'],
  ignorePatterns: ['dist'], //追加 .eslintignoreに対象外にしているが無いとコンパイルに時間がかかる
  /*-- ↓以下追加 --*/
  rules: {
    'no-use-before-define': 'off', //関数や変数が定義される前に使われているとエラーになるデフォルトの機能をoff
    '@typescript-eslint/no-use-before-define': ['error'], //typescript側のno-use-before-defineを使うようにする
    'import/prefer-default-export': 'off', //named exportがエラーになるので使えるようにoff
    '@typescript-eslint/no-unused-vars': 'off', //unused-importsを使うため削除
    'unused-imports/no-unused-imports': 'error', //不要なimportの削除
    'unused-imports/no-unused-vars': [
      //unused-importsでno-unused-varsのルールを再定義
      'warn',
      { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' },
    ],
    'no-param-reassign': [2, { props: false }], //パラメーターのプロパティ変更を許可
    'import/extensions': [
      //importのときに以下の拡張子を記述しなくてもエラーにしない
      'error',
      {
        js: 'never',
        ts: 'never',
      },
    ],

    'no-void': [
      //void演算子の許可
      'error',
      {
        allowAsStatement: true,
      },
    ],
  },
  settings: {
    'import/resolver': {
      //importするファイルをjsだけではなく、tsを含むファイルを許可する
      node: {
        paths: ['src'],
        extensions: ['.js', '.ts'],
      },
    },
  },
  /*-- ↑追加ここまで --*/
};

rulesの部分をカスタマイズしたい方は以下の公式サイトを参考にしてみてください。

ESLintでチェックしなくて良いフォルダやファイルを指定する場合は、.eslintignoreというファイルを作ってそこに指定していきます。今回は出力先のdistフォルダやwebpack.config.jsは除外しています。

node_modules/
dist/
webpack.config.js
.eslintrc.js

最後にVSCodeからESLintの拡張機能をインストールして、有効化しましょう。

Prettierの設定

Prettierはコードを設定したルールに基づいて整形してくれます。どのように整形するかは.prettierrc.jsというファイルを用意してそこに書いていきます。

以下は例です。

module.exports = {
  printWidth: 120,
  trailingComma: 'es5',
  tabWidth: 2,
  semi: true,
  singleQuote: true,
  endOfLine: 'lf',
};

指定できるoptionについては以下の公式サイトを参考にしてみてください。

設定が書けたら、VSCodeでPrettierの拡張機能をインストールして、有効化しましょう。

VSCode上でESLintとPrettierを機能させる

ファイルを保存したタイミングでESLintとPrettierを実行させるためにはVSCode上の設定も変更する必要があります。

キーボードの【 commnad + , 】で設定画面が開かれます。そしたら以下のアイコンを押して、ESLintとPrettierに必要な設定を加えます。

設定ファイルに加える内容は以下です。

"editor.defaultFormatter": "esbenp.prettier-vscode", //VSCodeではPrettierで整形する
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true // ファイル保存時に ESLint でフォーマット
  },
"editor.formatOnSave": true, //ファイルを保存したタイミングでフォーマットする

設定が保存できたら、1度VSCodeを再起動させましょう。
これでファイルを保存したタイミングで、ESLintとPrettierが機能するようになります。

webpack.config.jsの中身を説明する

webpack.config.jsに書いたTypeScriptとPugの設定について説明します。

読み込み先と出力先を指定する

entryでは読み込み先を指定して、outputでは出力先を指定します。

つまり、srcのjsフォルダの中にtsファイルを読み込み、distのjsフォルダへjsファイルでで出力させています。

//path モジュールの読み込み
const path = require('path');
//読み込むファイルを複数指定する用
const WebpackWatchedGlobEntries = require('webpack-watched-glob-entries-plugin');

const filePath = { //それぞれのパスを指定
  js: './src/js/',
  ejs: './src/ejs/',
  sass: './src/scss/',
};

//TypeScript読み込みの定義
const entriesTS = WebpackWatchedGlobEntries.getEntries([path.resolve(__dirname, `${filePath.js}*.ts`)])();

const app = {
  entry: entriesTS, //読み込むファイルを指定
  output: {
    filename: './js/[name].js', //どこに出力するのか指定
    path: path.resolve(__dirname, 'dist'), //出力先にフォルダを指定
    clean: true, //ビルドごとにdistフォルダの中を削除する
  },

仮想サーバーの立ち上げ

パッケージのwebpack-dev-serverを使うことで仮想サーバーを立ち上げることができます。必要な指定は以下が考えられます。(4系の場合)

  • static … 監視するフォルダを指定
  • hot … 差分ファイルのみ更新
  • open … ブラウザを立ち上げる
//仮想サーバーの設定(4系の書き方)
  devServer: {
    //ルートディレクトリの指定
    static: path.resolve(__dirname, 'src'),
    hot: true,
    open: true,
  },
  target: 'web', //targetは'web'にする

基本的には4系がオススメですが、3系を使っている場合は以下の指定が必要です。

  • contentBase … どのフォルダのファイルを表示させるか
  • open … ブラウザを自動で立ち上げるかどうか
  • watchContentBase … ファイルが更新されたら、表示も変更させるか
  • writeToDisk … 開発環境でもフォルダやファイルを出力させるかどうか
//仮想サーバーの設定(3系の書き方)
  devServer: {
    //ルートディレクトリの指定
    contentBase: path.join(__dirname, "dist"),
    //ブラウザを自動的に起動
    open: true,
    // ファイルを監視するかどうか
    watchContentBase: true,
    //バンドルされたファイルを出力するかどうか。お好みで。
    //writeToDisk: true
  },
  target: 'web', //targetは'web'にする

writeToDiskについて、指定がなければdistフォルダにはファイルもフォルダも出力されません。ただwebpack上でデータが保存されているため表示はされます。

開発している間もフォルダやファイルを出力させたいのであれば、writeToDiskをtrueにしておきましょう。

書き方の違いについては以下の記事を参考にしてみてください。

TypeScriptの指定について

TypeScriptのファイルは以下の部分で読み込んでいます。excludeでnode_moduleフォルダの中は除外しています。

{
  test: /\.ts$/,
  use: "ts-loader",
  exclude: /node_modules/
},

tsファイルでimportを使ったときに拡張子をつけるとエラーが表示されます。それを防ぐために以下の指定をしています。

resolve: {
 // 拡張子を配列で指定
 extensions: [".ts", ".js"]
},

Pugの指定について

Pugファイルを以下の指定で読み込んでいます。

{
 test: /\.pug$/,
   use: [
     {
       loader: "pug-loader",
         options: {
           pretty: true
         }
       }
   ]
}

Pugファイルが増えても自動でdistファイルに出力してくれる設定は以下です。

//path モジュールの読み込み
const path = require('path');
// HTMLの読み込み用
const HtmlWebpackPlugin = require('html-webpack-plugin');
//読み込むファイルを複数指定する用
const WebpackWatchedGlobEntries = require('webpack-watched-glob-entries-plugin');

const filePath = {
  js: './src/js/',
  pug: './src/pug/',
  sass: './src/scss/',
};

/* Pug読み込みの定義 */
const entries = WebpackWatchedGlobEntries.getEntries([path.resolve(__dirname, `${filePath.pug}**/*.pug`)], {
  ignore: path.resolve(__dirname, `${filePath.pug}**/_*.pug`),
})();
const htmlGlobPlugins = (entries) => {
  return Object.keys(entries).map(
    (key) =>
      new HtmlWebpackPlugin({
        //出力ファイル名
        filename: `${key}.html`,
        template: `${filePath.pug}${key}.pug`,
        //JS・CSS自動出力と圧縮を無効化
        inject: false,
        minify: false,
      })
  );
};

最後にhtmlGlobPlugins関数をpluginsで実行しています。

plugins: [
    ...htmlGlobPlugins(entries),
  ],

HtmlWebpackPluginのオプションについては以下です。

  • filename … ファイル名を指定
  • template … どのフォルダから読み込むのか指定
  • inject … trueにするとCSSファイルとJavascriptファイルの読み込みをHTMLファイルに書いてくれる。JavaScriptファイルはheadを指定すればhead内に、bodyを設定すればbodyが閉じる前に書き込まれる。
  • minify … HTMLファイルを圧縮するかどうか

Sassの指定について

ローダーは下から順番に読み込まれて処理されていきます。下から順番にローダーを書くと以下です。

  • sass-loader … SassをCSSに変換する。ここでDart Sassを指定。
  • postcss-loader … Autoprefixerを適用させている。
  • css-loader … CSS内のurl()メソッドの取り扱いについて。
//cssファイルの出力用
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

{
   // 対象となるファイルの拡張子(scss)
   test: /\.(sa|sc|c)ss$/,
   // Sassファイルの読み込みとコンパイル
   use: [
      // CSSファイルを抽出するように MiniCssExtractPlugin のローダーを指定
      {
        loader: MiniCssExtractPlugin.loader,
       },
        // CSSをバンドルするためのローダー
       {
          loader: 'css-loader',
          options: {
          //URL の解決を無効に
          url: false,
          // production モードでなければソースマップを有効に
          sourceMap: enabledSourceMap,
          // postcss-loader と sass-loader の場合は2を指定
          importLoaders: 2,
          // 0 => no loaders (default);
          // 1 => postcss-loader;
          // 2 => postcss-loader, sass-loader
          },
        },
        // PostCSS(autoprefixer)の設定
        {
          loader: 'postcss-loader',
          options: {
            // PostCSS でもソースマップを有効に
            sourceMap: enabledSourceMap,
            postcssOptions: {
            // ベンダープレフィックスを自動付与
              plugins: [require('autoprefixer')({ grid: true })],
             },
           },
         },
          // Sass を CSS へ変換するローダー
         {
           loader: 'sass-loader',
           options: {
              //  production モードでなければソースマップを有効に
              sourceMap: enabledSourceMap,
           },
         },
     ],
},

そしてcssファイルを出力については以下で書いています。

//path モジュールの読み込み
const path = require('path');
//読み込むファイルを複数指定する用
const WebpackWatchedGlobEntries = require('webpack-watched-glob-entries-plugin');

const filePath = { //それぞれのパスを指定
  js: './src/js/',
  ejs: './src/ejs/',
  sass: './src/scss/',
};

/* Sassファイル読み込みの定義*/
const entriesScss = WebpackWatchedGlobEntries.getEntries([path.resolve(__dirname, `${filePath.sass}**/**.scss`)], {
  ignore: path.resolve(__dirname, `${filePath.sass}**/_*.scss`),
})();

const cssGlobPlugins = (entriesScss) => {
  return Object.keys(entriesScss).map(
    (key) =>
      new MiniCssExtractPlugin({
        //出力ファイル名
        filename: `./css/${key}.css`,
      })
  );
};

最後にcssGlobPlugins関数をpluginsの部分で実行します。

plugins: [
    ...cssGlobPlugins(entriesScss),
  ],

Dart Sassを使ったサイト設計については以下に書いたので参考にしてみてください。

パッケージのライセンス情報をjsファイルに含める

通常だとライセンス情報が【ファイル名】.LICENSE.txtのようにテキストファイルで出力されます。ファイルを出力させず、jsファイルの中にライセンス情報を含めるために、terser-webpack-pluginを使用しています。

//terser-webpack-pluginの読み込み
const TerserPlugin = require("terser-webpack-plugin");

//パッケージのライセンス情報をjsファイルの中に含める
  optimization: {
    minimizer: [
      new TerserPlugin({
        extractComments: false
      })
    ]
  },

こちらの記事を参考にさせてもらいました。

画像の取り扱いについて

画像についてはsrcフォルダにあるimagesフォルダにアップすると、distフォルダのimagesフォルダに出力されるようになります。

srcフォルダの中にあるimagesフォルダに画像が1枚もないとエラーになるので注意しましょう。

plugins: [
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, 'src/images/'), //画像を読み込むフォルダを指定
          to: path.resolve(__dirname, 'dist/images'), //画像を出力するフォルダを指定
        },
      ],
    }),
  ],

webpackを動かす

webpackのコマンドを省略して打てるようにpackage.jsonに以下を追記します。

  "scripts": {
    "prod": "NODE_ENV=production webpack --mode production",
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch",
    "start": "webpack serve --mode development",
    "lint": "eslint --cache --fix ./src/js/ && prettier --write ./src/"
  },
  • prod … 本番環境として出力
  • dev … 開発環境として出力
  • watch … 開発環境としてファイルを監視、反映する
  • start …  仮想サーバーの立ち上げて、開発環境モードでファイルを監視する
  • lint … ESLintとPrettierを実行する

コマンドを打つときは、最初にnpm runをつけます

開発環境で作業を始めるときは以下のコマンドを打ちます。

npm run start

本番環境で出力するときはprodコマンドを打ちます。

npm run prod

Babelの設定について(古いブラウザ対応)

TypeScriptはES6からES5など、バージョンを切り替えてJavaScriptとして変換してくれますが、これだけでIE11に対応できるわけではありません。IE11のみ使えないプロパティやメソッドが多く存在するため、babelなどを使ってさらに変換する必要があります。

使えない機能については以下を参考にしてみてください。

例として、IE11では使えない以下の機能をTypeScriptを使ってES5でJavaScriptで変換してみます。

  • forEach
  • find
  • テンプレート文字列
  • Promise
let test: NodeList = document.querySelectorAll(".element");
test.forEach(function (elem, index) {
  console.log(elem);
});

const array1: number[] = [1, 19, 3, 160, 31];
const found = array1.find(function (element) {
  return element > 10;
});
console.log(found);

const text: string = "ほげほげ";
const massage = `メッセージは${text}です。`;
console.log(massage);

const promise = new Promise<void>((resolve) => {
  setTimeout(() => {
    resolve();
  }, 1000);
});
promise.then(() => {
  console.log("次の処理");
});

JavaScriptのES5で出力すると以下です。forEachやfindなどそのまま出力されているのでIE11では機能しません。

!(function () {
  "use strict";
  document.querySelectorAll(".element").forEach(function (o, n) {
    console.log(o);
  });
  var o = [1, 19, 3, 160, 31].find(function (o) {
    return o > 10;
  });
  console.log(o),
    console.log("メッセージはほげほげです。"),
    new Promise(function (o) {
      setTimeout(function () {
        o();
      }, 1e3);
    }).then(function () {
      console.log("次の処理");
    });
})();

Babelを使ってIE11対応する

IE11対策としてTypeScriptをBabelを通してからトランスパイルする方法があります。まずはwebpackにBabelを動かすのに必要な5つのパッケージをインストールします。

※@babel/polyfillは非推奨のため、core.jsを使います。

npm i -D @babel/core @babel/preset-env babel-loader @babel/preset-typescript core-js

webpack.config.jsの書き方も変わります。
先ほどまでの書き方は以下でした。

{
  test: /\.ts$/,
  use: "ts-loader",
  exclude: /node_modules/
},

そしてBabelを使って書くと以下のようになります。

{
  test: /\.(ts|js)$/,
  exclude: /node_modules/,
  use: [
    {
      loader: "babel-loader",
      // Babel のオプションを指定する
      options: {
        presets: [
          [
            "@babel/preset-env",
            {
              targets: {
                ie: 11,
                esmodules: true
               },
              useBuiltIns: "usage",
              corejs: { version: "3", proposals: true }
            }
          ],
          ["@babel/preset-typescript"]
        ]
      }
    }
  ]
},
  • targets … どのブラウザを対象のするか指定する
  • esmodules … trueだとES Moduleをターゲットにする。これがあるとbrowserslistに書かれた指定より、targetsにある指定が優先される
  • useBuiltIns … 必要なポリフィル分だけ自動で出力させる
  • corejs … インストールしているcore.jsのバージョンを明確に指定する必要がある

package.jsonでbrowserslistにIE11を指定しているなら、targetsの指定はいりません。例えばbrowserslistに別の指定がされていて、targetsにIE11の指定があり、esmodulesもtrueになっている場合は、targetsに書かれている指定が優先されます。

Babelの細かい指定については公式サイトを参照してください。

pakage.jsonでbrowserlistを書く場合は以下のようになります。

実際にトランスパイルしてみると以下のようにIE11で表示させるために必要なポリフィル分が出力されているのがわかります。

polyfill.ioを使ってIE11対応する

Babelを使わず、polyfillo.ioというサイトを使ってIE11対応することもできます。これはwebpackで特に設定する必要がなく、polyfillo.ioから必要なポリフィルを選択してリンクを読み込ませるだけです。

polyfillo.ioの良い点は、表示されたブラウザで足りないポリフィルのみ読み込みます。モダンなブザウザで問題なく表示できるのであれば、無駄なポリフィルは読み込まれません。またIE11対応が必要なくなったときに、リンク1行消すだけで済みます。

Babelでは対応できないappendやbefore、afterなどDOM機能のポリフィルが揃っているのも良いですね。

headeにリンクを貼るイメージは以下です。

CSSの出力について

webpackを通してSassファイルを読み込んで、CSSファイルとして出力させます。そのため、CSSファイルを出力させるためにはtsファイルにSassファイルをimportさせる必要があります。

// Sassファイルの読み込み
import "../scss/style.scss";

あとはpugファイルの_headの.ejs中にcssを読み込ませる指定を書きます。

head(prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# website: http://ogp.me/ns/website#")
    meta(charset="UTF-8")
    meta(name="viewport" content="width=device-width, initial-scale=1.0")
    link(rel="stylesheet" href="./css/style.css")
    script(defer src="./js/init.js")

Pugの出力について

Pugについては、srcの中にあるpugフォルダで管理しています。分割するheaderやfooterなどのファイルは「components」フォルダの中で_header.pugや_footer.pugのようにアンダーバーをつけて配置させます。

|_src/
   |- pug/ (共通で使うパーツ)
   |   |- components/
   |   |   |- _header.pug
   |   |   |- _footer.pug
   |
   |- index.pug

例えばindex.pugに_head.pugと_header.pug、_footer.pugを読み込ませる場合は以下のように書きます。

doctype html
html(lang="ja")
  include components/_head.pug
  body(itemscope itemtype="http://schema.org/WebPage")
    div.l-wrapper(itemscope itemprop="mainContentOfPage")
      include components/_header.pug
      main.l-main
        div.d-demo
          section.d-demo__section
      include components/_footer.pug

さいごに

今回はwebpack5でTypeScriptとPugの設定方法について説明させてもらいました。基本的にはTypeScriptでES5に変換できていればほとんどのブラウザで表示が可能です。

IE11は2022年6月15日でサポートが終了するので、もう対策する必要はないかもしれませんが、念のため書いておきました。参考にしてみてください。