お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。
投稿日:
ソフトウェア::zsh

確認環境

Env Ver
zsh zsh 5.8 (x86_64-pc-msys)

サンプルコード

例として.zshrcに書くものとする(別に分割しても構わない)

  1. setopt PROMPT_SUBSTする
    1. この設定によりパラメータ展開、コマンド置換、および算術展開がプロンプトで実行される
  2. 展開したい関数をシングルクォートで囲み文字列結合する
    1. 注意点
      1. 変数に代入している場合評価されない
      2. 関数はシングルクォートで囲まないと評価されない
# baz は echo が入った関数
PROMPT "foobar"'$(baz)'
# 以下の 2 つは期待通り動作しない
PROMPT "foobar"$(baz)
PROMPT $(baz)
投稿日:
Node.js::その他

Babelとtscどっちがいいのか気になったので調べて見たメモ
結論から言うと基本的にはtscで良い

確認環境

Env Ver
@babel/cli 7.14.5
@babel/core 7.14.6
@babel/preset-env 7.14.7
@babel/preset-typescript 7.14.5
typescript 4.3.2

Babelとは何か?

What is Babel? よりBabelとはJavaScriptのコンパイラと説明されている

Babelがしてくれること

  • 構文の変換
  • corejsを使ったPolyfill
  • ソースコードの変換(codemods
  • その他色々

基本的にはES6+をES6にしてくれると考えれば良さそうです
でもそれって別にtscでもいいいよねって思う

Babelの導入

基本はこれ

npm i -D @babel/core @babel/cli @babel/preset-env

Babelの設定

.babelrcを作ってその中にJSONを書いていく
こんな感じ

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "edge": "17",
          "ie": "11",
          "safari": "11.1"
        },
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ]
}

Babelの機能について

コンポーネント

@babel/cli

BabelのCLI、これがないと始まらない

@babel/core

Babel本体、CLIがよしなにしてくれる

@babel/preset-env

構文の変換やPolyfillを設定できる

@babel/preset-typescript

TSをトランスパイルしてくれる

BabelとBrowserlist

BabelはBrowserlistの設定を認識して自動でPolyfillを挿入してくれます
なお、Babel 7.4.0以前では設定方法が異なる可能性があります

サンプルコード

IE11をターゲットにした設定のサンプルです
以下のコマンドを流せばPolyfillされたJSが出ることを確認できます
npx babel src -d dest --extensions ".ts"

.babelrc
{
  "presets": [
    ["@babel/preset-env", { "corejs": 3, "useBuiltIns": "usage" }],
    ["@babel/preset-typescript"]
  ]
}
.browserlist
ie 11

Babelとtscでトランスパイルしてみる

割と違うコードが出てきます

元のソース

const sp = new URLSearchParams('?aaa=bbb&ccc');
console.log(sp);
const prm = new Promise((res) => res(true));
console.log(prm);
[...Array(10)].forEach((_, i) => console.log(i));

console.log(globalThis.Date());

export {};

Babel

.babelrc
{
  "presets": [["@babel/preset-env"], ["@babel/preset-typescript"]]
}
ビルド結果
"use strict";

Object.defineProperty(exports, "__esModule", {
    value: true,
});

function _toConsumableArray(arr) {
    return (
        _arrayWithoutHoles(arr) ||
        _iterableToArray(arr) ||
        _unsupportedIterableToArray(arr) ||
        _nonIterableSpread()
    );
}

function _nonIterableSpread() {
    throw new TypeError(
        "Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
    );
}

function _unsupportedIterableToArray(o, minLen) {
    if (!o) return;
    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
    var n = Object.prototype.toString.call(o).slice(8, -1);
    if (n === "Object" && o.constructor) n = o.constructor.name;
    if (n === "Map" || n === "Set") return Array.from(o);
    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
        return _arrayLikeToArray(o, minLen);
}

function _iterableToArray(iter) {
    if (
        (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null) ||
        iter["@@iterator"] != null
    )
        return Array.from(iter);
}

function _arrayWithoutHoles(arr) {
    if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}

function _arrayLikeToArray(arr, len) {
    if (len == null || len > arr.length) len = arr.length;
    for (var i = 0, arr2 = new Array(len); i < len; i++) {
        arr2[i] = arr[i];
    }
    return arr2;
}

var sp = new URLSearchParams("?aaa=bbb&ccc");
console.log(sp);
var prm = new Promise(function (res) {
    return res(true);
});
console.log(prm);

_toConsumableArray(Array(10)).forEach(function (_, i) {
    return console.log(i);
});

console.log(globalThis.Date());

tsc

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "allowJs": true,
    "checkJs": true,
    "sourceMap": true,
    "outDir": "./dist",
    "strict": true,
    "noImplicitAny": true,
    "moduleResolution": "node",
    "isolatedModules": true,
    "baseUrl": ".",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"]
}
ビルド結果
"use strict";
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
        to[j] = from[i];
    return to;
};
Object.defineProperty(exports, "__esModule", { value: true });
var sp = new URLSearchParams('?aaa=bbb&ccc');
console.log(sp);
var prm = new Promise(function (res) { return res(true); });
console.log(prm);
__spreadArray([], Array(10)).forEach(function (_, i) { return console.log(i); });
console.log(globalThis.Date());
//# sourceMappingURL=index.js.map

結論

tscで問題ない

tscの方が綺麗なコードが出てきてるので、Polyfill要らなければtscで問題ないです
因みにES6+をES5にするのもできるのでJSのトランスパイルにも使えます

core-jsを使うならBabel

ちょいちょい触っててBabelの利点はcore-jsがあれば.browserlistを使えるので、そこでターゲットを指定してやればPolyfillを勝手に差し込んでくれるところですね

但しCRAではBabelに対する.browserlistはほぼ無価値

あとはCreate React AppはビルドにBabelを採用しているので、FW側でビルドパイプラインがあるときには採用したほうが楽です
(態々書き換える意味もないので)
ただreact-scripts 4.0.3にはcore-jsが入っていないので、基本的に.browserlistを書いたところでPolyfillは入らないため、あんまり存在感はないです

投稿日:
Node.js::webpack

バンドル内容をHTMLに注入するの続き

Webpackには次の2つの開発モードがあるdevelopment, production

開発モードの設定

modeプロパティに'development', 'production', 'none'を設定する
これを設定するとprocess.env.NODE_ENVで値を取得できる様になる
それぞれの違いは mode-development にあるが、試してみた感じproductionは出力が最適化され、console系が消えるものと思われる
noneを設定するとminifyとかがされてない生のコードが出てくる

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
+  mode: 'development', 
  entry: {
    index: './src/index.js',
    print: './src/print.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
  plugins: [
     new HtmlWebpackPlugin({
       title: 'Webpack',
       template: 'index.html'
     })
  ]
};
投稿日:
Node.js::webpack

基本的なバンドルの続き

複数JSの個別バンドル

src/print.jsを追加して、関係するファイルも幾らか変更する

src/print.js

export const printMe = () => {
  console.log('I get called from print.js!');
}

src/index.js

import _ from 'lodash';
-import './index.css';
-import Icon from './icon.png';
+import { printMe } from './print';

function component() {
+  window.onload = printMe;
  
  const div = document.createElement('div');
  div.innerHTML = _.join(['Hello', 'webpack'], ' ');
-    div.classList.add('hello');
-
-    const img = document.createElement('img');
-    img.src = Icon;
-    div.appendChild(img);
  
  return div;
}

document.body.appendChild(component());

webpack.config.js

const path = require('path');

module.exports = {
-   entry: './src/index.js',
+  entry: {
+    index: './src/index.js',
+    print: './src/print.js',
+  },
   output: {
-    filename: 'bundle.js',
+    filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
-   module: {
-    rules: [
-      {
-        test: /\.css$/i,
-        use: ['style-loader', 'css-loader'],
-      },
-      {
-        test: /\.(png|svg|jpg|jpeg|gif)$/i,
-        type: 'asset/resource',
-      },
-    ],
-  },
};

この状態でバンドルするとindex.bundle.jsとprint.bundle.jsが生えてくるが、前回バンドルした残骸が残ってしまう

バンドル時に出力フォルダをクリーンアップする

webpack.config.js

const path = require('path');

module.exports = {
  entry: {
    index: './src/index.js',
    print: './src/print.js',
  },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
+     clean: true,
   },
};

バンドルした内容をHTMLに反映する

この時点のindex.htmlはdist/bundle.jsを参照しているので、今回の結果が反映されていません
これが反映されるように修正します

npm i -D html-webpack-plugin

webpack.config.js

const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    index: './src/index.js',
    print: './src/print.js',
  },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
   },
+   plugins: [
+      new HtmlWebpackPlugin({
+        title: 'Webpack',
+      })
+   ]
};

これで設定したバンドルファイルがindex.htmlに反映されるようになりました

HTMLのテンプレートを用意する

しかし先程の設定では出力されるHTMLにどうしても限界があります
HtmlWebpackPlugin のリファレンスを見ることである程度なんとかなりますが、テンプレートに差し込むこともできます

次のように変えることでテンプレートHTMLにバンドルを差し込むことが確認できます

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    index: './src/index.js',
    print: './src/print.js',
  },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
   },
   plugins: [
      new HtmlWebpackPlugin({
        title: 'Webpack',
+        template: 'index.html'
      })
   ]
};

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
-    <title>Getting Started</title>
  </head>
  <body>
    <script src="dist/bundle.js"></script>
+    <h1>Webpack</h1>
  </body>
</html>
投稿日:
Node.js::webpack

webpackとはなんぞやというところで軽く調べてみたメモ
ほぼ公式のチュートリアル
やれることが非常に多いので公式ドキュメントを読むのが一番良い

確認環境

Env ver
webpack 5.41.0
webpack-cli 4.7.2

webpackとは

JavaScriptのモジュールバンドラで次の機能を持つ

  • JSやCSS、画像ファイルやデータファイルなどの各種静的ファイルを出力フォルダにまとめて吐く
  • ブラウザのキャッシュの影響を回避するために、出力ファイル名を毎回変える
  • index.htmlに必要なファイル参照を自動で埋め込む
  • 出力フォルダのクリーンアップ
  • HMR用のサーバーを起動する
  • Sourcemapを吐く
  • 次の構成を利用したビルドパイプラインを構成する
    • TypeScript, CoffeeScript, Babel and JSX
  • その他色々
    • 豊富なAPIと設定が存在するのでいろんな事ができる!

webpackを試してみる

取り敢えずこんな感じのプロジェクトを作る

  1. npm init
  2. npm i -D webpack webpack-cli lodash
|- package.json
|- /dist
|- index.html
|- /src
  |- index.js

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Getting Started</title>
  </head>
  <body>
    <script src="dist/main.js"></script>
  </body>
</html>

src/index.js

import _ from 'lodash';

function component() {
  const div = document.createElement('div');
  div.innerHTML = _.join(['Hello', 'webpack'], ' ');

  return div;
}

document.body.appendChild(component());

npx webpackを流してバンドルしてあげるとlodashが組み込まれたJSが吐き出される

CSSのバンドル

  1. npm i -D style-loader css-loaderを流して
  2. webpack.config.jssrc/index.cssを作成
  3. src/index.jsもちょっと書き換えます

webpack.config.js

const path = require('path');

module.exports = {
   entry: './src/index.js',
   output: {
    filename: 'bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
   module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

src/index.css

.hello {
  color: red;
}

src/index.js

import _ from 'lodash';
+import './index.css';

function component() {
  const div = document.createElement('div');
  div.innerHTML = _.join(['Hello', 'webpack'], ' ');
+  div.classList.add('hello');

  return div;
}

document.body.appendChild(component());

この状態でnpx webpackを流すとCSSのバンドルが確認出来る

assetsのバンドル

webpack.config.js

const path = require('path');

module.exports = {
   entry: './src/index.js',
   output: {
    filename: 'bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
   module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
+      {
+        test: /\.(png|svg|jpg|jpeg|gif)$/i,
+        type: 'asset/resource',
+      },
    ],
  },
};

src/index.js

import _ from 'lodash';
import './index.css';
+import Icon from './icon.png';

function component() {
  const div = document.createElement('div');
  div.innerHTML = _.join(['Hello', 'webpack'], ' ');
  div.classList.add('hello');

+  const img = document.createElement('img');
+  img.src = Icon;
+  div.appendChild(img);

  return div;
}

document.body.appendChild(component());

フォントファイルやデータファイルのバンドル

基本は同じなので公式のガイドを見るとわかりやすい