webpack v4でIE11 (ES5) 向けバンドルを作る
「なんで今さら?」
そういう要件があるのだ。MicrosoftによるIE11のサポートは終わっていないし(TeamsやOffice 365の対応は終わった&終わるけど)
というより、サポートを終わらせるために告知のダイアログを作る。そしてそのダイアログは、アプリ本体がIE11に対応せずクラッシュするようになったあとも動かしたい。ダイアログ単体をIE11でも動くJSとして出力したい。
ちなみにダイアログをちょろっと出すだけなので、大きなパッケージに依存したりポリフィルを入れたりはしないです。なので本当にシンプルなトランスパイル&バンドルだけになります。
基本のwebpack.config
// @ts-check
const path = require('path');
/** @type {import("webpack").Configuration} */
const config = {
entry: path.resolve(__dirname, 'src/index.ts'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js',
},
};
module.exports = config;
// @ts-check
や @type
などで少しでもVS Codeの補完の恩恵を得る。@types/webpack
のインストールが必要。
module.exports = { ... }
と書いてしまうと、補完は効くけど、赤線は引いてくれなくなるので、分けて書いてます。
ビルドコマンドは
npx webpack --mode production
import { foo } from './common';
foo();
export function foo() {
const message = 'foo';
console.info(message);
}
このままだと './common'
が解決できずに失敗する。
$ webpack --mode production
Hash: e8a52a7ba0cf55d60158
Version: webpack 4.46.0
Time: 56ms
Built at: 2021/05/13 17:41:48
1 asset
Entrypoint main = my-first-webpack.bundle.js
[0] ./src/index.ts 40 bytes {0} [built]
ERROR in ./src/index.ts
Module not found: Error: Can't resolve './common' in '/Users/kazuma/work/src'
@ ./src/index.ts 1:0-31 3:0-3
error Command failed with exit code 2.
拡張子 .ts
はデフォルトでは解決できないので。次の設定にする(.js
が解決できなくなるが構わない)
// @ts-check
const path = require('path');
/** @type {import("webpack").Configuration} */
const config = {
entry: path.resolve(__dirname, 'src/index.ts'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js',
},
+
+ resolve: {
+ extensions: ['.ts'],
+ },
};
module.exports = config;
👏
$ webpack --mode production
Hash: b3ef52cfb0d7315fe3b5
Version: webpack 4.46.0
Time: 87ms
Built at: 2021/05/13 17:43:26
Asset Size Chunks Chunk Names
my-first-webpack.bundle.js 971 bytes 0 [emitted] main
Entrypoint main = my-first-webpack.bundle.js
[0] ./src/index.ts + 1 modules 117 bytes {0} [built]
| ./src/index.ts 40 bytes [built]
| ./src/common.ts 77 bytes [built]
✨ Done in 0.85s.
ts-loaderがなくても動いているように見えるが、それは拡張子が .ts
なだけでTS特有の文法がないから。
export function foo() {
- const message = 'foo';
+ const message: string | null = 'foo';
console.info(message);
}
$ webpack --mode production
Hash: 3d3933751c042a4ea898
Version: webpack 4.46.0
Time: 192ms
Built at: 2021/05/13 17:54:40
1 asset
Entrypoint main = my-first-webpack.bundle.js
[0] ./src/common.ts 330 bytes {0} [built] [failed] [1 error]
[1] ./src/index.ts 40 bytes {0} [built]
ERROR in ./src/common.ts 2:15
Module parse failed: Unexpected token (2:15)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| export function foo() {
> const message: string | null = 'foo';
|
| console.info(message);
@ ./src/index.ts 1:0-31 3:0-3
error Command failed with exit code 2.
// @ts-check
const path = require('path');
/** @type {import("webpack").Configuration} */
const config = {
entry: path.resolve(__dirname, 'src/index.ts'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js',
},
+ module: {
+ rules: [
+ {
+ test: /\.ts$/,
+ use: 'ts-loader',
+ exclude: /node_modules/,
+ },
+ ],
+ },
resolve: {
extensions: ['.ts'],
},
};
module.exports = config;
$ webpack --mode production
Hash: c6dd3d494f3696851b6b
Version: webpack 4.46.0
Time: 2624ms
Built at: 2021/05/13 17:58:23
Asset Size Chunks Chunk Names
my-first-webpack.bundle.js 971 bytes 0 [emitted] main
Entrypoint main = my-first-webpack.bundle.js
[0] ./src/index.ts + 1 modules 117 bytes {0} [built]
| ./src/index.ts 39 bytes [built]
| ./src/common.ts 78 bytes [built]
✨ Done in 4.14s.
あ、tsconfig.jsonを忘れていた。tsconfig.jsonでトランスパイルが通る状態にしつつ、noEmit: false
(falseが
デフォルト値なので、あえて noEmit: true
にしなければOK)にしておけばOK。
// https://www.typescriptlang.org/ja/tsconfig
{
// src フォルダーだけを対象にする。
"include": ["./src/**/*"],
"compilerOptions": {
//
// プロジェクト設定
//
// tsc 以外のツールが TypeScript をトランスパイルしやすいようにいくつかの記法を禁止する。
"isolatedModules": true,
// IE11 で使える API を許可する。
"lib": ["ES5", "DOM"],
// ES Module 記法で書くための設定。
"module": "ESNext",
// IE11 で使える文法を許可する。
"target": "ES5",
//
// 厳密なチェック
//
// スクラッチで書くなら strict=true.
"strict": true,
//
// モジュール解決
//
// `import * as React from "react"` の代わりに `import React from "react"` と書ける。
"allowSyntheticDefaultImports": true,
// allowSyntheticDefaultImports のランタイム版といったイメージ。
"esModuleInterop": true,
// import 対象の探し方を決める。後方互換を求める理由がなければ原則 Node.
"moduleResolution": "Node",
//
// ソースマップ
//
//
// Linter Checks
//
// インデックスアクセスは null チェックが必要。
"noUncheckedIndexedAccess": true,
//
// 実験的な機能
//
//
// Advanced
//
// モジュール名(=ファイル名)の大文字/小文字を厳密に区別する。
// ファイル名の大文字/小文字が区別されない OS で役に立つ。
"forceConsistentCasingInFileNames": true,
// tsc コマンドは自分のソースコードの型チェックだけに用いる(パッケージ間の整合性まではチェックしない)。
"skipLibCheck": true
}
}
module=ESNextにしたけど自信がない。import文はどうせwebpackがハンドリングするのでなんでもいいかなと思いESNextにしたが、ランタイムに違いが生まれるのだろうか。
結構違いがありそう?やってることは一緒か?
(-
は module=CommonJS, +
は module=ESNext)
@@ -90,18 +90,16 @@
/*!***********************!*\
!*** ./src/common.ts ***!
\***********************/
-/*! no static exports found */
-/***/ (function(module, exports, __webpack_require__) {
+/*! exports provided: foo */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
-
-Object.defineProperty(exports, "__esModule", { value: true });
-exports.foo = void 0;
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "foo", function() { return foo; });
function foo() {
var message = 'foo';
console.info(message);
}
-exports.foo = foo;
/***/ }),
@@ -110,14 +108,14 @@ exports.foo = foo;
/*!**********************!*\
!*** ./src/index.ts ***!
\**********************/
-/*! no static exports found */
-/***/ (function(module, exports, __webpack_require__) {
+/*! no exports provided */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _common__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./common */ "./src/common.ts");
-Object.defineProperty(exports, "__esModule", { value: true });
-var common_1 = __webpack_require__(/*! ./common */ "./src/common.ts");
-common_1.foo();
+Object(_common__WEBPACK_IMPORTED_MODULE_0__["foo"])();
/***/ })
雑★に検索したところ、ESNextがいい?AMDがいい?という情報で迷う感じに。
よくわからんからTreeShakeの有効な(いやこの規模でかつ目的的にそんなの必要なパッケージに依存はしないだろうが)ESNextにしておくか。実機で動作確認して動かなければCommonJS, AMDを試せばいい。
module=ESNextでバンドルしたJSがIE11で動いた。これで設定はよし。
あとはロジックを書いていくだけ。とはいってもinsertAdjacentHTMLでダイアログHTMLをねじ込むだけだが。string literalが使えるので書きやすいはず。
画像やCSSが必要だった。css-loaderやfile-loaderの設定も追加する。
style-loaderとcss-loaderの使い方忘れた
@@ -16,6 +16,21 @@ const config = {
use: 'ts-loader',
exclude: /node_modules/,
},
+
+ {
+ test: /\.css$/,
+ use: [
+ {
+ loader: 'style-loader',
+ },
+ {
+ loader: 'css-loader',
+ options: {
+ modules: true,
+ },
+ },
+ ],
+ },
],
},
resolve: {
"css-loader": "3.6.0",
と少し古めなので、*.module.css
を自動で拾ってくれない。のでオプションを明示的に設定した。
CSSファイルを眺めながら文字列でクラス名を揃える、ということはやりたくなかったので typed-css-modules を使った。とても便利。頻繁には実行しないのでnpx実行にしておいた。
file-loaderではなくurl-laoderを加える。画像は小さめなのでできればONE JSにパックしたいため。
@@ -31,6 +31,11 @@ const config = {
},
],
},
+
+ {
+ test: /\.png$/,
+ use: 'url-loader',
+ },
],
},
resolve: {