Web制作向けnpm-scripts 3



ついにIEのサポートが終了したので、更新しました。
[Web制作向けnpm-scripts 2](https://qiita.com/takeshisakuma/items/5c6b0ff729f2c010e1b2)
[Web制作向けnpm-scripts](https://qiita.com/takeshisakuma/items/dbbb1c465099e6e4dd2e)

### 変更点
- IE対応廃止(JSのバンドル、polyfili自動追加、トランスパイル)
- webp対応(JPG、PNGもwebpに変換)
- ESModule化

### 補足
- node.js v18.7.0、npm 7.7.4で作成
- avif対応は時期尚早のため見送り


### ファイル構成
```:directory
 website
 ├ src
 │　├ pug
 │　├ img
 │　├ scss
 │　└ ts
 ├ package.json
 ├ package-lock.json
 ├ imagemin.js
 ├ jest.config.js
 ├ tsconfig.json
 ├ .browserslistrc
 ├ .eslintrc.json 
 ├ .prettierrc.json　
 ├ .stylelintrc.json
 └ .stylelintignore 
```

## npm-scriptsの準備

```:terminal
 npm init -y
 
 npm i -D npm-run-all
 npm i -D chokidar-cli
```

## ホットリロード

### Browser-Sync

#### インストール


```:terminal
　npm i -D browser-sync
```

#### npmコマンド追加
```:package.json
"scripts": {
  "start:server": "browser-sync start -s dist -w src/*.html src/css/*.css src/js/*.js"
}
```

#### 動作確認
```:terminal
npm run start:server 
```

ブラウザが立ち上がれば成功です。
まだデータが無いのでエラーで問題ありません。
Ctrl+Cで終了します。


## Pug

### インストール

```:terminal
npm i -D github:pugjs/pug-cli#master
```

### npmコマンド追加

```:package.json
"scripts": {
  "compile:pug": "pug src/pug/ --hierarchy -o dist/ -P",
  "watch:pug": "pug src/pug/ --hierarchy -o dist/ -w -P",
}
```

### 動作確認

/src/pug/配下にPugファイルを設置して、変換してみてください。
[Pugの記法まとめ](https://qiita.com/takeshisakuma/items/fdcf456d8250e6dafc7b)

```:terminal
//1回変換
npm run compile:pug

//Pugファイルの変更を監視して変換
npm run watch:pug
```
/src/pug/配下のPugファイルが/dist/配下にHTMLとして変換されれば成功です。



## CSS

### Sass(Dart-Sass)
SCSSファイルをCSSファイルに変換します。

#### インストール
```:terminal
npm i -D sass
``` 

### autoprefixer
CSSのプロパティにベンダープレフィックスを付与します。

#### インストール]
```:terminal
npm i -D postcss-cli
npm i -D autoprefixer
npm i -D postcss
```

#### 対象ブラウザの指定
##### 設定例
```:.browserslistrc
defaults
```
設定は下記URLを参考に適宜変更してください。
[browserslist](https://github.com/browserslist/browserslist)

[.browserslistrcで対象ブラウザを指定する](https://qiita.com/takeshisakuma/items/0bc1c39ee976bd1f52d7)

[.browserslistrcに設定した対象ブラウザの確認](https://browserl.ist/)

##### 対象ブラウザの確認
```:terminal
npx browserslist
```

#### npmコマンド追加
```:pacjage.json
"scripts": {
  "compile:css2cssprefix": "postcss dist/css/style.css -u autoprefixer -o dist/css/style.css",
  "compile:scss2css": "sass src/scss/style.scss:dist/css/style.css --style=expanded --source-map-urls=relative",
  "compile:scss2cssprefix": "run-s compile:scss2css compile:css2cssprefix",
  "watch:scss2cssprefix": "chokidar \"src/scss/\" --command \"npm run compile:scss2cssprefix\" --initial", 
}
```

#### 動作確認
/src/scss/配下にSCSSファイルを設置して、変換してみてください。

[Sassの記法(SCSS構文)](https://qiita.com/takeshisakuma/items/1c40c42f61c6e751c0e3)

```:terminal
//SCSSファイルをベンダープレフィックス付きでCSSに変換(1回)
npm run compile:scss2cssprefix

//SCSSファイルをベンダープレフィックス付きでCSSに変換(SCSSファイルを監視して変更される度)
npm run watch:scss2cssprefix
```

/src/scss/配下のSCSSファイルが/dist/css/配下にCSSファイルとして変換されれば成功です。







## TypeScript
JavaScriptへの変換を行います。

### インストール
```:terminal
npm i -D typescript
``` 


### 設定ファイルの準備
tsconfig.jsonを作成します。

```:terminal
tsc --init
```



```:tsconfig.json
{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    /* Basic Options */
    // "incremental": true,                         /* Enable incremental compilation */
    "target": "ESNEXT" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
    "module": "ESnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
    // "lib": [],                                   /* Specify library files to be included in the compilation. */
    // "allowJs": true,                             /* Allow javascript files to be compiled. */
    // "checkJs": true,                             /* Report errors in .js files. */
    // "jsx": "preserve",                           /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
    // "declaration": true,                         /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                      /* Generates a sourcemap for each corresponding '.d.ts' file. */
    "sourceMap": true,                           /* Generates corresponding '.map' file. */
    // "outFile": "./",                             /* Concatenate and emit output to single file. */
    "outDir": "./dist/js" /* Redirect output structure to the directory. */,
    "rootDir": "./src/ts"  /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
    // "composite": true,                           /* Enable project compilation */
    // "tsBuildInfoFile": "./",                     /* Specify file to store incremental compilation information */
    // "removeComments": true,                      /* Do not emit comments to output. */
    // "noEmit": true,                              /* Do not emit outputs. */
    // "importHelpers": true,                       /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,                  /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,                     /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true /* Enable all strict type-checking options. */,
    // "noImplicitAny": true,                       /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                    /* Enable strict null checks. */
    // "strictFunctionTypes": true,                 /* Enable strict checking of function types. */
    // "strictBindCallApply": true,                 /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,        /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                      /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                        /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                      /* Report errors on unused locals. */
    // "noUnusedParameters": true,                  /* Report errors on unused parameters. */
    // "noImplicitReturns": true,                   /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,          /* Report errors for fallthrough cases in switch statement. */
    // "noUncheckedIndexedAccess": true,            /* Include 'undefined' in index signature results */
    // "noPropertyAccessFromIndexSignature": true,  /* Require undeclared properties from index signatures to use element accesses. */

    /* Module Resolution Options */
    // "moduleResolution": "node",                  /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                             /* Base directory to resolve non-absolute module names. */
    // "paths": {},                                 /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                              /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                             /* List of folders to include type definitions from. */
    // "types": [],                                 /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,        /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
    // "preserveSymlinks": true,                    /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,                /* Allow accessing UMD globals from modules. */

    /* Source Map Options */
    // "sourceRoot": "",                            /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                               /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,                     /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                       /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,              /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,               /* Enables experimental support for emitting type metadata for decorators. */

    /* Advanced Options */
    "skipLibCheck": true /* Skip type checking of declaration files. */,
    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
  },
  "exclude": ["node_modules"]
}
``` 
 


### 動作確認

```:/src/ts/index.ts
import { importTest } from "./sub";

const message = "TypeScriptの変換成功";

console.log(message);
console.log(importTest);
```

```:/src/ts/sub.ts
export const importTest: string = "sub.tsからのimport成功";
```

```:terminal
//TypeScriptファイルをJavsScriptファイルに変換
npm run compile:ts

//TypeScriptファイルをJavsScriptファイルに変換(TypeScriptファイルを監視して変更される度)
npm run watch:ts
```

/src/ts/配下のTypeScriptファイルが/dist/js/配下にJavsScriptファイルとして変換されれば成功です。












## 画像圧縮

対応するファイル形式
- jpg
- png
- webp
- gif
- svg
(jpgとpngはwebpに変換します)

### インストール


```:terminal
npm i -D imagemin-cli
npm i -D imagemin-keep-folder
npm i -D imagemin-gifsicle
npm i -D imagemin-svgo
npm i -D imagemin-webp
```

### npmコマンド追加
```:package.json
"scripts": {
 "watch:img": "chokidar \"src/img/**/*\" --command \"node imagemin.js\" --initial"
}
```

### 設定ファイル
```:imagemin.js
import keepfolder from "imagemin-keep-folder";
import gifsicle from "imagemin-gifsicle";
import svgo from "imagemin-svgo";
import webp from "imagemin-webp";
 
keepfolder(["src/img/**/*.{jpg,png,gif,svg,webp}"], {
  plugins: [
    gifsicle(),
    svgo(),
    webp({
      quality: 50,
    }),
  ],
  replaceOutputDir: (output) => {
    return output.replace(/img\//, "../dist/img/");
  },
});
``` 
 




```:package.json
//追加
"type": "module",
```


### 動作確認
/src/img/配下に画像を格納します。(jpg、png、gif、svg、webp)

```:terminal
npm run watch:img
```

/src/img/配下の画像が圧縮されて/dist/img/配下にコピーされれば成功です。









## distディレクトリの削除
以前の作業で生成したファイルがdistに残っているとトラブルにつながる恐れがあるため、削除します。

### インストール
```:terminal
npm i rimraf -D
```

### npmコマンド追加
```:package.json
"scripts": {
 "delete:dist": "rimraf dist"
}
```

### 動作確認
```:terminal
npm run delete:dist
```

distディレクトリが削除されれば成功です。








## ファイルのコピー
今回は、sitemap.xml、robots.txt、humans.txt、manifest.json、favicon.ico、apple-touch-icon.pngをコピーするようにします。
あまり変更が入らないファイルのコピーを想定しているので、-w オプションは付けません。

### インストール
```:terminal
npm i -D cpx
```

### npmコマンド追加
```:package.json
"scripts": {
  "copy:sitemapxml": "cpx \"src/sitemap.xml\" dist/",
  "copy:robotstxt": "cpx \"src/robots.txt\" dist/",
  "copy:humanstxt": "cpx \"src/humans.txt\" dist/",
  "copy:manifestjson": "cpx \"src/manifest.json\" dist/",
  "copy:faviconico": "cpx \"src/favicon.ico\" dist/",
  "copy:apple-touch-iconpng": "cpx \"src/apple-touch-icon.png\" dist/"
}
```

### 動作確認
```:terminal
 npm run copy:sitemapxml
```

/src/sitemap.xmlが/dist/sitemap.xmlにコピーされれば成功です。






## タスクの連携
これまで作ってきたタスクを連携して、コマンド1つで実行できるようにします。

### npmコマンドの追加
```:package.json
"scripts": {
  "prewatch:all": "run-s delete:dist copy:sitemapxml copy:robotstxt copy:humanstxt copy:manifestjson copy:faviconico copy:apple-touch-iconpng",
  "watch:all": "run-p watch:pug watch:scss2cssprefix watch:ts watch:img start:server"
}
```

### 動作確認
```:terminal
npm run watch:all
```
HTML、CSS、JavaScriptのコンパイルを並列処理しているので、表示が崩れている場合は完了タイミングが異なっているのが原因と思われますので、リロードしてみてください。


## コード整形
### prettier
#### インストール
```:terminal
npm i -D prettier 
```

```:.prettierrc.json
{
  "semi": true,
  "trailingComma": "all",
  "printWidth": 120,
  "tabWidth": 2,
  "useTabs": false,
  "proseWrap": "preserve",
  "endOfLine": "auto"
}
```
設定は下記URLを参考に適宜変更してください。

[.prettierrc](https://qiita.com/takeshisakuma/items/bbb2cd2f1c65de70e363)

### stylelint
#### インストール
```:terminal
npm i -D stylelint
npm i -D stylelint-config-recommended-scss
npm i -D stylelint-config-prettier

npm i -D stylelint-scss
npm i -D stylelint-order
npm i -D stylelint-prettier
```

#### npmコマンド追加
```:package.json
"scripts": {
  "lint:scss": "stylelint \"**/*.scss\"",
  "format:scss": "stylelint --fix \"**/*.scss\""
}
```

#### 設定ファイル
```:.stylelintrc.json
{
  "extends": [
    "stylelint-config-recommended-scss",
    "stylelint-config-prettier"
  ],
  "plugins": [
    "stylelint-order",
    "stylelint-prettier"
  ],
  "rules": {
    "prettier/prettier": true,
    "indentation": 2,
    "declaration-colon-newline-after": null,
    "value-list-comma-newline-after": "never-multi-line",
    "order/properties-alphabetical-order": true
  }
}
```
設定は下記URLを参考に適宜変更してください。
[.stylelintrc](https://qiita.com/takeshisakuma/items/bbb2cd2f1c65de70e363)

#### 動作確認
```:terminal
//SCSSファイルのリント
npm run lint:scss

//SCSSファイルの整形
npm run format:scss
```





### Eslint
#### インストール
```:terminal
npm i -D eslint
npm i -D @typescript-eslint/parser
npm i -D eslint-config-prettier
```  

#### npmコマンド追加

```:package.json
"scripts": {
  "lint:ts": "eslint src/ts/**/*.ts",
  "format:ts": "eslint src/ts/**/*.ts --fix"
}
```

#### 設定ファイル

| envの設定値 | 内容 | 
|-----------|------------|
|"browser": true|無いとdocumentやconsoleなどでエラーが出てしまう|
|"node":true|	無いとwebpack.config.jsonのdirname、module、requireでエラーが出てしまう|


```:.eslintrc.json
{
  "parser": "@typescript-eslint/parser",
  "extends": [
    "eslint:recommended",
    "prettier"
  ],
  "parserOptions":  {
    "ecmaVersion": "latest",
    "sourceType":  "module"
  },
  "env": {
    "browser": true,
    "node": true
  },
  "root": true,
  "rules": {
    "quotes": ["error", "double"], //クォーテーションがシングルのときエラー
    "indent": ["error", 2]
  }
}
``` 
設定は下記URLを参考に適宜変更してください。
[Configuring ESLint](https://eslint.org/docs/user-guide/configuring)

#### 動作確認
```:terminal
//TypeScriptファイルのリント
npm run lint:ts

//TypeScriptファイルの整形
npm run format:ts
```

### SCSS、TypeScript一括整形
```:package.json
"scripts": {
"format:all":"run-p format:scss format:ts"
}
```

#### 動作確認

```:terminal
//TypeScriptファイル、SCSSファイルの整形
npm run format:all
```





## commit前にコード整形
### インストール
```:terminal
npm i -D simple-git-hooks
npm i -D lint-staged
```


|コマンド|内容|
|---|---|
|pre-commit|コミット前に実行|
|pre-push|プッシュ前に実行|

```:package.json
"simple-git-hooks": {
  "pre-commit": "npx lint-staged"
},
 
"lint-staged": {
 "src/ts/*.ts": [
   "prettier --write",
   "eslint --fix --quiet"
 ],
 "src/scss/*.scss": [
   "prettier --write",
   "stylelint --fix --quiet"
 ]
}
```

### pre-commit、pre-pushの有効化
```:terminal
npx simple-git-hooks
git/hooks/pre-commit、.git/hooks/pre-pushというファイルが作られます。
git commitを行うと、lint-stagedに指定したコマンドが実行されます。
```








## テスト
Jestを使えるようにします。

### インストール
```:terminal
npm i -D jest
npm i -D ts-jest
npm i -D @types/jest
```

### npmコマンド追加
```:package.json
"scripts": {
  "test": "jest"
}
```

### 設定ファイル
```:jest.config.js
export default {
  roots: ["<rootDir>/src/ts/"],
  testMatch: ["**/__tests__/**/*.+(ts|tsx|js)", "**/?(*.)+(spec|test).+(ts|tsx|js)"],
  transform: {
    "^.+\\.(ts|tsx)$": "ts-jest",
  },
};
``` 

### eslintrc.jsonの変更
Eslintでテストコードにエラーが出ないように、jestを追加

```:.eslintrc.json
"env": {
  "browser": true,
  "node":true,
  "jest": true
},
```
#### コードのサンプル
##### テスト用サンプルコード
```:src\ts\test_for_jest.ts
export const calc = (x: number, y: number) => {
  return x + y;
}
```
##### テストコード
```:src\ts\__tests__\test_for_jest.spec.ts
import { calc } from "../test_for_jest";

describe("関数calcの動作確認", () => {
 it("3+4=7になる", () => {
   expect(calc(1, 8)).toBe(9);
 });
});
```


.spec.tsを変換しないようにtsconfigの設定変更
```:tsconfig.json
  "exclude": ["node_modules"]
  ↓
  "exclude": ["node_modules", "**/*.spec.ts"]
```

#### 動作確認
```:terminal
npm run test
```





## 本番公開対応
/dist/配下のCSS、JavaScriptのminifyを行います。

### インストール
```:terinal
npm i -D uglify-es
npm i -D clean-css-cli
```

### npmコマンド追加
```:package.json
"scripts": {
  "min:css":"cleancss -o dist/css/style.css dist/css/style.css",
  "min:js": "uglifyjs dist/js/main.js -o dist/js/main.js",
  "prepare:all":"run-p min:js min:css"
}
```

### 動作確認
```:terminal
npm run prepare:all
```

/dist/配下のファイルをアップロードします





## まとめ
### 使用時準備
```:termina;
git clone https://github.com/takeshisakuma/testament.git
cd testament
npm ci
npx simple-git-hooks
```

### 制作時
```:terminal
npm run watch:all
```

### コード整形時
```:terminal
npm run format:all
```

### テスト
```:terminal
npm run test
```
### minify

```:terminal
npm run prepare:all
```
dist配下のファイルをそのままアップロードします


## リポジトリ

https://github.com/takeshisakuma/testament